Web::Scraperでscrapeを繰り返し呼ぶときに注意すること

| コメント(0) | トラックバック(0)

前にも書きましたが、バイト先でWeb::Scraperを使ってます。

スクレイピング対象数が膨大な場合にちょっと困ったことが起きたので、今日はそれのmemo。
何が起こったかというと、スクレイピング先のサーバの調子が悪くて、500エラーなどを返したときに例外処理を書いていないとコケてしまうのだ。

例えば当ブログの最近の各エントリーページからのタイトル部分を引っ張ってくるとしよう。


#!/usr/local/bin/perl
use strict;
use Web::Scraper;
use URI;
use YAML;
binmode STDOUT => ":utf8";

my @uri = qw(https://hoge.sub.jp/blog/archives/2007/11/post_303.html
             https://hoge.sub.jp/blog/archives/2007/11/post_3000.html #桁違い
             https://hoge.sub.jp/blog/archives/2007/11/post_302.html);

my $scraper = scraper {
  process "h3", "title" => "TEXT";
};

for (@uri){
  my $result = $scraper->scrape(URI->new($_));
  print Dump($result);
}

実行すると
> perl test.pl
---
title: てくのー
GET https://hoge.sub.jp/blog/archives/2007/11/post_3000.html failed: 404 Not Found at test.pl line 17
2番目のURLは現在では存在しないのでWeb::Scraperがcroak を呼び出す。この為、素のloopで回している上記のコードはループの途中でこけてしまう。


・対策方法その1
一番ラクなのはevalブロックで問題の箇所をくくって例外処理だろう。
evalブロック内でdieやcroak等でコケた場合、特殊変数$@(それにしても$@って、google先生キラーな名前だよなぁ)にエラー内容が入る。下記の場合はWeb::Scraper内のcroakに渡された文字列が入る。


#!/usr/local/bin/perl
use strict;
use Web::Scraper;
use URI;
use YAML;
binmode STDOUT => ":utf8";

my @uri = qw(https://hoge.sub.jp/blog/archives/2007/11/post_303.html
             https://hoge.sub.jp/blog/archives/2007/11/post_3000.html
             https://hoge.sub.jp/blog/archives/2007/11/post_302.html);

my $scraper = scraper {
  process "h3", "title" => "TEXT";
};

for (@uri){
  my $result;
  eval {$result = $scraper->scrape(URI->new($_))};
  #例外処理
  if($@){
    print "exception! $@\n"
  } else {
    print Dump($result);
  }
}

実行してみる
> perl test2.pl
---
title: てくのー
exception! GET https://hoge.sub.jp/blog/archives/2007/11/post_3000.html failed: 404 Not Found at test2.pl line 18

---
title: 時間つぶしはニコニコ動画に限るよ。
ok。最後まで走った。


・対策方法2
plaggerプラグインのplugins/CustomFeed-Script/muhyojo.plを見て初めて知ったのだが、scrapeに渡すのってURIオブジェクトに限らないのね。
確かにWeb::Scraperのソースを見てみると


sub scrape {
    my $self  = shift;
    my($stuff, $current) = @_;

    my($html, $tree);

    if (blessed($stuff) && $stuff->isa('URI')) {
        require Encode;
        require HTTP::Response::Encoding;
        my $ua  = $self->user_agent;
        my $res = $ua->get($stuff);
        if ($res->is_success) {
            my @encoding = (
                $res->encoding,
                # could be multiple because HTTP response and META might be different
                ($res->header('Content-Type') =~ /charset=([\w\-]+)/g),
                "latin-1",
            );
            my $encoding = first { defined $_ && Encode::find_encoding($_) } @encoding;
            $html = Encode::decode($encoding, $res->content);
        } else {
            croak "GET $stuff failed: ", $res->status_line;
        }
        $current = $stuff->as_string;
    } elsif (blessed($stuff) && $stuff->isa('HTML::Element')) {
        $tree = $stuff->clone;
    } elsif (ref($stuff) && ref($stuff) eq 'SCALAR') {
        $html = $$stuff;
    } else {
        $html = $stuff;
    }
  …(略)

と、いった感じでURIオブジェクト以外も受け付けてくれるようだ。ただ、代入しているだけなのでその場合は自分でdecode等の処理をかけないとまずい。
んで、できたのが、↓


#!/usr/local/bin/perl
use strict;
use Web::Scraper;
use URI;
use YAML;
binmode STDOUT => ":utf8";
use Encode;
use DateTime;

my @uri = qw(https://hoge.sub.jp/blog/archives/2007/11/post_303.html
             https://hoge.sub.jp/blog/archives/2007/11/post_3000.html
             https://hoge.sub.jp/blog/archives/2007/11/post_302.html);

my $scraper = scraper {
  process "h3", "title" => "TEXT";
};

for my $url (@uri){
  my $response = $scraper->user_agent->get($url);
  unless ($response->is_success) {
    #ここで例外処理
    print "GET $url failed: " . $response->status_line . ' : ' . DateTime->now . "\n";
    next;
  }

  my ($encoding) = $response->header('Content-Type') =~ /charset=([\w\-]+)/g;
  my $result = $scraper->scrape( Encode::decode($encoding, $response->content) );
  print Dump($result);
}

encodingの取得はWeb::Scraperを参考にした。よくよく考えたら、日付情報加えただけなのであんまlogは変化してない...。これでは$@とカワランのでダメな例ですね(´Д`;)

とりあえず実行してみる。
> perl test3.pl
---
title: てくのー
GET https://hoge.sub.jp/blog/archives/2007/11/post_3000.html failed: 404 Not Found : 2007-11-19T14:24:06
---
title: 時間つぶしはニコニコ動画に限るよ。

個人的にはやっぱ$@の方が楽かなーという印象。URIオブジェクトではなく、GRTした内容をscrapeに投げる場合は色々とごにょごにょしなきゃいけないのが最大のネックかなー。

トラックバック(0)

トラックバックURL: https://hoge.sub.jp/blog-cgi/mt/mt-tb.cgi/1375

このブログ記事について

このページは、Lyoが2007年11月19日 22:25に書いたブログ記事です。

ひとつ前のブログ記事は「てくのー」です。

次のブログ記事は「魂ってなんだろう?」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

OpenID対応しています OpenIDについて
Powered by Movable Type 7.9.3