Web::Scraper の実装がカッコいいぞという話
この記事は、"Perl Advent Calendar 2023" 16日目の記事です。
Web::Scraper というPerlモジュールがあります。名前の通り、Webページをスクレイピングするためのモジュールです。2007年にリリースされたモジュールです。このモジュールで育った人も多いのではないかと思います。
このモジュールの scrape
メソッドは以下のような引数を受け付けます。
$res = $scraper->scrape(URI->new($uri));
$res = $scraper->scrape($html_content);
$res = $scraper->scrape(\$html_content);
$res = $scraper->scrape($http_response);
$res = $scraper->scrape($html_element);
URIオブジェクト、HTML文字列かそのリファレンス、HTTP::Responseオブジェクト、HTML::Elementオブジェクト、という風に、いろいろな引数を受け入れてくれて便利です。
scrape
の中身はどうなっているのでしょうか。
sub scrape {
my $self = shift;
my($stuff, $current) = @_;
my($html, $tree);
if (blessed($stuff) && $stuff->isa('URI')) {
my $ua = $self->user_agent;
my $res = $ua->get($stuff);
return $self->scrape($res, $stuff->as_string);
} elsif (blessed($stuff) && $stuff->isa('HTTP::Response')) {
if ($stuff->is_success) {
$html = $stuff->decoded_content;
} else {
croak "GET " . $stuff->request->uri . " failed: ", $stuff->status_line;
}
$current ||= $stuff->request->uri;
} elsif (blessed($stuff) && $stuff->isa('HTML::Element')) {
$tree = $stuff->clone;
} elsif (ref($stuff) && ref($stuff) eq 'SCALAR') {
$html = $$stuff;
} else {
$html = $stuff;
}
$tree ||= $self->build_tree($html);
URIオブジェクトがきたら HTTP::Response を取得して scrape
に再帰します。
HTTP::ResponseがきたらHTMLを取得します。
HTML::Elementがきたらオブジェクトをクローンしておきます。
そして、HTTP::Response由来のHTMLか、引数で渡されたHTMLから、HTML::TreeBuilder をベースとする HTML::TreeBuilder::XPath で HTMLをパースします。
最終的に $tree
に収れんしていく感じがしびれますね。特に再帰してるところがヤバい(語彙)
Web::Scraper は scrape
メソッドの多様な引数を受けるところだけでなく、コードリファレンスを受け付ける scraper
メソッド本体の実装も Perlらしさ全開のカッコよさなので是非読んでみてください。