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 が行われます。
- Web::Scraper を 16 倍速くする HTML::TreeBuilder::LibXML を書いた - TokuLog 改メ Perlを極めて起業する日記
- HTML::TreeBuilder::LibXML - search.cpan.org
id:miyagawa さんの Web::Scraper で、HTML::TreeBuilder::XPath の代わりに XML::LibXML を使うととても幸せになれそうなので実験してます。
XML::LibXML に手を出す前に IRC で「tinyxpath とか htmlcxx とか使って xpath 周りを高速にしたい」とかボヤいてことがありました。そのときに、id:vkgtaro さんや id:tomyhero さんに激しく libxml や XML::LibXML をオススメされました。libxml をオススメしてもらえてなかったら、確実に路頭に迷ってました。
以下が、変更したファイルと差分です。
- http://pub.woremacx.com/Web-Scraper/Scraper.pm
- http://pub.woremacx.com/Web-Scraper/Web-Scraper-LibXML.patch (差分)
ブランチにいれていただきました
使い方
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
