Mojo::DOMとWeb::Scraper

すごい適当だけど、手元のWin32マシン(XP @2.40GHz)でこんな感じ。WWW::Mixi::Scraperとか、Mojoliciousベースで書き換えてもいいかもなーとか思った(しないけど)。ちなみにテキスト抽出時の空白文字の扱いがMojo::DOMとWeb::Scraperで微妙に違うので、$test = 1にするとテストがひとつこけます。あと、抽出するものが単純なら正規表現やらなにやらでごりっと抜いた方が速いはず。

追記:Craftworksさんからブクマでご指摘いただいたように、Web::Scraper::LibXMLを使えば桁違いに速いですね。すっっかり忘れていました。LibXMLありなし版のベンチを同時にとるのは面倒だったので、::LibXMLに差し替えた場合の結果も追記しておきました。ありがとうございます。>Craftworksさん

cf. http://www.perlmonks.org/?node_id=926959

Mojo::DOMと素のWeb::Scraperの場合

perl: v5.12.3
Mojolicious: 3.05
Web::Scraper: 0.36
HTML::Selector::XPath: 0.12

              Rate webscraper2  webscraper        mojo
webscraper2 9.29/s          --         -1%        -40%
webscraper  9.40/s          1%          --        -40%
mojo        15.5/s         67%         65%          --

             Rate webscraper       mojo
webscraper 7.62/s         --       -43%
mojo       13.4/s        76%         --

             Rate webscraper       mojo
webscraper 8.53/s         --       -49%
mojo       16.8/s        97%         --

Mojo::DOMとWeb::Scraper::LibXMLの場合

perl: v5.12.3
Mojolicious: 3.05
Web::Scraper: 0.36
HTML::Selector::XPath: 0.12
XML::LibXML: 2.0002

              Rate        mojo  webscraper webscraper2
mojo        15.6/s          --        -76%        -80%
webscraper  64.6/s        315%          --        -15%
webscraper2 76.2/s        389%         18%          --

             Rate       mojo webscraper
mojo       13.3/s         --       -89%
webscraper  118/s       794%         --

             Rate       mojo webscraper
mojo       15.8/s         --       -86%
webscraper  114/s       622%         --
use strict;
use warnings;
use 5.010;
use Path::Extended;
use Web::Scraper;
use Mojolicious;
use Mojo::UserAgent;
use Mojo::DOM;
use Data::Dump qw/dump/;
use Benchmark qw/cmpthese/;
use Test::More;
use Test::Differences;

say "perl: $^V";
for (qw/Mojolicious Web::Scraper HTML::Selector::XPath/) {
  say "$_: " . $_->VERSION;
}

my $test = 0;

for (qw( www.yahoo.co.jp )) {
  my $file = file($_.'.html');
  my $html;
  if ($file->exists) {
    $html = $file->slurp;
  } else {
    my $ua = Mojo::UserAgent->new;
    # pretend to be IE 8
    $ua->name('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)');
    $html = $ua->get("http://$_/")->res->body;
    $file->save($html);
  }

  if ($test) {
    ok get_links_mojo($html);
    ok get_topics_mojo($html);
    ok get_shortcut_mojo($html);

    eq_or_diff [get_links_mojo($html)]    => [get_links_scraper($html)];
    eq_or_diff [get_topics_mojo($html)]   => [get_topics_scraper($html)];
    eq_or_diff [get_shortcut_mojo($html)] => [get_shortcut_scraper($html)];
  }

  {
    my $scraper = scraper { process 'a' => 'links[]' => '@href' };
    cmpthese(100, {
      mojo     => sub { get_links_mojo($html) },
      scraper  => sub { get_links_scraper($html) },
      scraper2 => sub { get_links_scraper2($html, $scraper) },
    });
  }

  {
    cmpthese(100, {
      mojo     => sub { get_topics_mojo($html) },
      scraper  => sub { get_topics_scraper($html) },
    });
  }

  {
    cmpthese(100, {
      mojo     => sub { get_shortcut_mojo($html) },
      scraper  => sub { get_shortcut_scraper($html) },
    });
  }
}

done_testing if $test;

sub get_links_mojo {
  my $html = shift;
  my $dom = Mojo::DOM->new($html);
  @{ $dom->find('a')->map(sub { shift->{href} }) };
}

sub get_links_scraper {
  my $html = shift;
  my $scraper = scraper { process 'a' => 'links[]' => '@href' };
  @{ $scraper->scrape($html)->{links} };
}

sub get_links_scraper2 {
  my ($html, $scraper) = @_;
  @{ $scraper->scrape($html)->{links} || [] };
}

sub get_topics_mojo {
  my $html = shift;
  my $dom = Mojo::DOM->new($html);
  @{ $dom->find('div#topicsfb ul.emphasis li')->map(sub { shift->all_text(0) }) };
}

sub get_topics_scraper {
  my $html = shift;
  my $scraper = scraper { process 'div#topicsfb ul.emphasis li' => 'topics[]' => 'TEXT' };
  @{ $scraper->scrape($html)->{topics} || [] };
}

sub get_shortcut_mojo {
  my $html = shift;
  my $dom = Mojo::DOM->new($html);
  $dom->at('ul.shortcut li a[href="r/pnp"]')->text;
}

sub get_shortcut_scraper {
  my $html = shift;
  my $scraper = scraper { process 'ul.shortcut li a[href="r/pnp"]' => pnp => 'TEXT'; result 'pnp' };
  $scraper->scrape($html);
}