仕事がテンパってきたので,現実逃避中

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"と書いておけば用が足ります.