woremacxの日記 RSSフィード

本家: blog.woremacx.com

2008-02-02

Web::Scraper を XML::LibXML で爆速にする hack!

この記事は、0.24 時代の Web::Scraper に関するエントリです。

通常の Web::Scraper で LibXML を使いたい用途であれば、Web::Scraper::LibXML をご利用ください。

0.29 以降の Web::Scraper には Web::Scraper::LibXML が追加されています。この Web::Scraper::LibXML を use することにより、HTML::TreeBuilder::LibXML が呼び出され、高速な parse が行われます。




id:miyagawa さんの Web::Scraper で、HTML::TreeBuilder::XPath の代わりに XML::LibXML を使うととても幸せになれそうなので実験してます。

XML::LibXML に手を出す前に IRC で「tinyxpath とか htmlcxx とか使って xpath 周りを高速にしたい」とかボヤいてことがありました。そのときに、id:vkgtaro さんや id:tomyhero さんに激しく libxml や XML::LibXML をオススメされました。libxml をオススメしてもらえてなかったら、確実に路頭に迷ってました。

以下が、変更したファイルと差分です。

ブランチにいれていただきました

使い方

use Web::Scraper;
Web::Scraper->use_libxml(1);

あとはふつうにつかうます。

ベンチ

http://www.junodownload.com/breaks/this-week/ とかそこらへんのスクレイピングに時間がかかる例です。(vaginarepos に WWW::JunoDownload という形で作ってるコードの一部です)

    my $s = scraper {
        process 'table.product_list > tr', 'products[]' => scraper {
            process '//td[@colspan="8"]',
                product_type => 'TEXT',
            process 'td.productcover > a > img',
                thumb => '@src';
            process '//td[@class="productartist"]/a[2]',
                artist => 'TEXT',
            process '//td[@class="producttitle"]/a[3]',
                name => 'TEXT',
                link => '@href';
            process '//td[@class="productlabel"]/a[2]',
                label => 'TEXT',
            process 'td.productdate',
                date => 'TEXT';
            process 'td.productprice',
                price => 'TEXT';
        };
    };

    my $res = $self->scrape($s, $url);

    my @products;
    my $product_type = undef;
    my %product_types = ( album => "Albums", single => "Singles" );
    for my $product (@{ $res->{products} }) {
        $product_type = $product->{product_type} if exists($product->{product_type}) and $product->{product_type} =~ /^(Albums|Singles)/;
        if (!$product_type and exists($opt->{type})) {
            $product_type = $product_types{$opt->{type}};
        }
        $product->{type} = $product_type;
        push(@products, $product) if $product->{name};
    }

ぼくのマシンでのベンチ結果

爆速ですね!と言いたいところですが、ぼくのマシンでやるベンチは信用に値しない><

# 同じページを3回スクレイピング
$ perl -Ilib bench-same-page.pl
Benchmark: timing 3 iterations of 0 XML::LibXML, 1 HTML::TreeBuilder::XPath...
0 XML::LibXML:  3 wallclock secs ( 2.31 usr +  0.03 sys =  2.34 CPU) @  1.28/s (n=3)
            (warning: too few iterations for a reliable count)
1 HTML::TreeBuilder::XPath: 49 wallclock secs (47.68 usr +  0.13 sys = 47.81 CPU) @  0.06/s (n=3)
            (warning: too few iterations for a reliable count)

# 異なるページを3回スクレイピング
$ perl -Ilib bench.pl
Benchmark: timing 1 iterations of 0 XML::LibXML, 1 HTML::TreeBuilder::XPath...
0 XML::LibXML:  2 wallclock secs ( 2.00 usr +  0.04 sys =  2.04 CPU) @  0.49/s (n=1)
            (warning: too few iterations for a reliable count)
1 HTML::TreeBuilder::XPath: 64 wallclock secs (60.44 usr +  0.03 sys = 60.47 CPU) @  0.02/s (n=1)
            (warning: too few iterations for a reliable count)

$ perl -Ilib bench.pl
Benchmark: timing 10 iterations of 0 XML::LibXML, 1 HTML::TreeBuilder::XPath...
0 XML::LibXML: 21 wallclock secs (18.63 usr +  0.23 sys = 18.86 CPU) @  0.53/s (n=10)
1 HTML::TreeBuilder::XPath: 786 wallclock secs (706.97 usr +  0.54 sys = 707.51 CPU) @  0.01/s (n=10)

ご協力いただいたベンチ結果

1ループで4ページスクレイピングするベンチ。

10倍以上の差はあるようです。ご協力ありがとうございました。

id:tomyhero さん
$ perl -Ilib bench.pl 
Benchmark: timing 100 iterations of 0 XML::LibXML, 1 HTML::TreeBuilder::XPath...
0 XML::LibXML: 62 wallclock secs (51.21 usr +  5.52 sys = 56.73 CPU) @  1.76/s (n=100)
1 HTML::TreeBuilder::XPath: 768 wallclock secs (699.20 usr +  8.68 sys = 707.88 CPU) @  0.14/s (n=100)

id:hideden さん
$ perl -Ilib bench.pl
Benchmark: timing 100 iterations of 0 XML::LibXML, 1 HTML::TreeBuilder::XPath...
0 XML::LibXML: 41 wallclock secs (40.45 usr +  0.46 sys = 40.91 CPU) @  2.44/s (n=100)
1 HTML::TreeBuilder::XPath: 628 wallclock secs (626.42 usr +  1.11 sys = 627.53 CPU) @  0.16/s (n=100)

Web::Scraper のテストでこける例

これはなんとかしたい。

t/12_html...............1..5
not ok 1 - script
# @@ -1,3 +1,4 @@
# -function foo() {
# +<![CDATA[function foo() {
#    return bar;
# -}+}
# +]]>

#   Failed test 'script
# @@ -1,3 +1,4 @@
# -function foo() {
# +<![CDATA[function foo() {
#    return bar;
# -}+}
# +]]>'
#   in t/12_html.t at line 25.
ok 2 - a
not ok 3 - div

#   Failed test 'div'
#   in t/12_html.t at line 25.
#          got: '
# <p>foo
# bar</p>
# <p>bar</p>'
#     expected: '<p>foo bar</p><p>bar</p>'
ok 4 - non-ascii
ok 5 - textarea
# Looks like you failed 2 tests of 5.
dubious
        Test returned status 2 (wstat 512, 0x200)
DIED. FAILED tests 1, 3
        Failed 2/5 tests, 60.00% okay

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。