仕事がテンパってきたので,現実逃避中
Web::Scraperのメモ
0.05版を元に書いてます.バージョンがあがると使い方も変わってくると思うので,新しい情報を参考にしてください.間違いあったら指摘してもらえるとうれしいです.
サンプルを見る
付属の例を見てみましょう.
#!/usr/bin/perl use strict; use warnings; use URI; use lib "lib"; use Web::Scraper; my $uri = shift @ARGV or die "URI needed"; my $scraper = scraper { process "a[href]", "urls[]" => '@href'; result 'urls'; }; my $links = $scraper->scrape(URI->new($uri)); use YAML; warn Dump $links; my $scraper = scraper { process "a[href]", "urls[]" => '@href'; result 'urls'; };
この例では,Web::Scraper::scraperを呼び出して,
{ process "a[href]", "urls[]" => '@href'; result 'urls'; };
というコードを引数として渡しています.$scraperはWeb::Scraperオブジェクトで,渡したコードは$Web::Scraper::coderefに格納しています.コードをどう処理しているかは後で...
my $links = $scraper->scrape(URI->new($uri));
は,処理のきっかけになる文で,本体はこんな感じ.
40 sub scrape { 41 my $self = shift; 42 my($stuff) = @_; 43 44 my($html, $tree); 45 46 if (blessed($stuff) && $stuff->isa('URI')) { 47 require Encode; 48 require HTTP::Response::Encoding; 49 my $ua = __ua; 50 my $res = $ua->get($stuff); 51 if ($res->is_success) { 52 $html = $res->encoding ? $res->decoded_content : Encode::decode("latin-1", $res->content); 53 } else { 54 croak "GET $stuff failed: ", $res->status_line; 55 } 56 } elsif (blessed($stuff) && $stuff->isa('HTML::Element')) { 57 $tree = $stuff->clone; 58 } elsif (ref($stuff) && ref($stuff) eq 'SCALAR') { 59 $html = $$stuff; 60 } else { 61 $html = $stuff; 62 }
$stuffに解析対象のページ情報がはいります.$stuffがURIオブジェクトの場合,組み込みのLWP::UsrAgentを使ってデータを取りに行きます.
24 sub __ua { 25 require LWP::UserAgent; 26 $ua ||= LWP::UserAgent->new(agent => __PACKAGE__ . "/" . $VERSION); 27 $ua; 28 }
agent名を定義しているので,google様にむげにはじかれる事もないですし,HTTP::Response::Encodingを使っているので,文字コード変換も自動で行います.$stuffがHTML::Elementの場合や,すでに取得したテキストの場合にも対応しています.
後回しにした部分の説明
{ process "a[href]", "urls[]" => '@href'; result 'urls'; };
という箇所は,aタグが着いてhref属性が着いているエレメントを探し出し,それらエレメントのhref属性をurlsという名前の配列に突っ込め,という意味になります.
processの最初の引数にはセレクタを書きます.二つ目の引数の処理はこんな風になっているので,
106 if (ref($key) && ref($key) eq 'CODE' && !defined $val) { 107 for my $node (@nodes) { 108 local $_ = $node; 109 $key->($node); 110 } 111 } elsif ($key =~ s!\[\]$!!) { 112 $stash->{$key} = [ map __get_value($_, $val), @nodes ]; 113 } else { 114 $stash->{$key} = __get_value($nodes[0], $val); 115 } 116 }
t/04_callback.tのように関数か
my $s = scraper { process $block->selector, sub { my $node = shift; push @value, eval $block->callback; fail $@ if $@; }; };
答えがひとつしかいらない場合には,eg/ebay-auction.plのような文字列か
process "h3.ens>a", description => 'TEXT', url => '@href';
複数個必要な場合には,上で取り上げた例のように末尾に"[]"を付けた文字列を書きます.
my $scraper = scraper { process "a[href]", "urls[]" => '@href'; result 'urls'; };
三つ目の引数は,
125 if (ref($val) && ref($val) eq 'CODE') { 126 local $_ = $node; 127 return $val->($node); 128 } elsif (blessed($val) && $val->isa('Web::Scraper')) { 129 return $val->scrape($node); 130 } elsif ($val =~ s!^@!!) { 131 return $node->attr($val); 132 } elsif (lc($val) eq 'content' || lc($val) eq 'text') { 133 return $node->as_text; 134 } else { 135 Carp::croak "Unknown value type $val"; 136 }
と,処理しているので,Web::Scraper 勝手に添削 - Bulknews::Subtech - subtechにあるように,関数を書いたり,
process 'span.comment', 'comment[]' => sub { my $text = $_->as_text or return; URI::Find->new(sub { '' })->find(\$text); return $text =~ /[\s\x20-\x7e]{30}/ ? $text : (); };
t/03_scraper_nest.tのようにscraperを使う
process $block->selector, 'friends[]' => scraper { process 'a', href => '@href', };
ことができます.でも一般的には,属性値が欲しいなら"@href"のようにセレクタを記載するか,値そのものがほしければ,"content"か"text"と書いておけば用が足ります.