Perl で CSS セレクタ

rubyスクレイピングして web の情報を取得するのには、今まで正規表現xpath でやってたので、わりと面倒でした。で、ふと scrAPI というスクレイピングツールキットを知ったのですが、これがかなり便利そう。
このツールキットを使うと、CSS3 なセレクタを記述することで、要素を取得することができます。

という Ruby の scrAPI での CSS セレクタがいい感じでございますなあと指をくわえて見てたんだけど、

Per discussions in CSS Selector in Perl, I made a quick perl module HTML::Selector::XPath, which is available at http://svn.bulknews.net/repos/public/HTML-Selector-XPath/trunk/ now.

と miyagawa さんがモジュールを作ってました。CSS セレクタXpath に変換するというもの。おお熱い。氏曰く、コードは Prototype の CSS セレクタの内部処理を Xpath に変換してやるパッチ あたりからインスパイアだそうな。これがあれば PerlCSS セレクタスクレイピングできる!

trunk から checkout してちょっと試してみました。

use HTML::TreeBuilder::XPath;
use Smart::Comments;

my $selector = HTML::Selector::XPath->new('ul.bookmarkinfo li.favorited');
my $xpath = $selector->to_xpath;
### $xpath

という感じで CSS セレクタな文字列から XPath を生成して出力すると、

### $xpath: '//ul[contains(concat(\' \', @class, \' \'), \' bookmarkinfo \')]
//li[contains(concat(\' \', @class, \' \'), \' favorited \')]'

と、確かに XPath に変換されてるキタコレ。*1

id を入力すると、その id がはてなブックマークで何件お気に入られてるかを調べるスクリプト、とかもこんな感じで簡単に書けます。

#!/usr/local/bin/perl
use strict;
use warnings;

use URI::Fetch;
use HTML::Selector::XPath;
use HTML::TreeBuilder::XPath;

my $id = shift or die "usage: $0 <id>";
my $res = URI::Fetch->fetch(sprintf 'http://b.hatena.ne.jp/%s/', $id)
    or die URI::Fetch->errstr;

my $tree = HTML::TreeBuilder::XPath->new;
$tree->parse($res->content);
$tree->eof;

my @nodes = $tree->findnodes(selector('ul.bookmarkinfo li.favorited'));

printf "User '%s' is favorited from %d users\n", $id,
    $nodes[0]->content->[1];

sub selector {
    HTML::Selector::XPath->new(shift)->to_xpath;
}

実行すると、

% perl favorited_count.pl jkondo
User 'jkondo' is favorited from 683 users

と出る。楽チン! すばらしー。夢がひろがりんぐ。イベントドリブンで指定したシンボルに代入できるようなフレームワークでラップすれば scrAPI の完成ですな。

あと、この HTML::Selector::XPath のテストが最近話題の Test::Base を使っててイカス、というところも見逃せません。

*1:途中で改行してます