Web::Scraper で XPath と CSS セレクタを混ぜて使う例

Web::Scraper はいたれりつくせりの仕掛けが仕込んであって、便利ですね。私が、割と良く使っている機能は以下 2 つです。

  1. process の第一引数に、CSS セレクタだけでなく、XPath も指定できます。ただし、XPath を指定するときは先頭を必ずスラッシュ(/)で始めなければいけません。
  2. process の第二引数以降の、値をどこから取得するかを指定する部分に、コード・リファレンスを置くこともできます。これを使うと、DOM ツリー中の値を加工して抽出することができます。

具体例として、デイリーポータルZアーカイブ一覧の中からべつやくれいさんのエントリを抽出してみることにします。まず、アーカイブ・ページのエントリ部分を取り出してやると、こうなっています。

<TD width="580" valign="top" class="tx12px">
  <P>
    <B><FONT color="#FF0000">日付</FONT></B>
    <BR>
    <A href="26/c/"></A>
    <FONT class=tx10px color=#666666>著者名</FONT><BR>
    一行要約
  </P>
</TD>

一つ注意しなければならないのは、font 要素の class 属性と color 属性の値がきまぐれに変化し、これらの属性を使って要素を区別するのは、うまくいかなさそうなことです。ここでは、b 要素の中に入れ子になっているかどうかで、日付と著者の区別することにします。
まず、上の記述の DOM 要素 p を抽出できたとして、1個分のエントリの抽出をおこなう scraper を作ります。

# uri は相対パスのまんまの簡易バージョン
my $entry = scraper {
    process 'p>b>font', 'date' => 'text';
    process 'p>a', 'title' => 'text', 'link' => '@href';
    process 'p>font', 'author' => 'text';
    result 'link', 'date', 'title', 'author';
};

もう一度、上のマークアップを眺めてみると、URI の記述が相対パスになっています。扱いやすくするには、これを絶対 URI に変換しておきたいものです。そこで、変数 $base_uri にあらかじめドキュメントの URI をセットしておき、相対パスから絶対 URI へ変換するのに利用することにしました。

# uri を絶対 URI に変換するバージョン
my $base_uri;
my $entry = scraper {
    process 'p>b>font', 'date' => 'text';
    process 'p>a',
        'title' => 'text',
        'link' => sub { URI->new_abs($_->attr('href'), $base_uri)->as_string };
    process 'p>font', 'author' => 'text';
    result 'link', 'date', 'title', 'author';
};

a エレメントから uri を取り出すさいに、そのまま @href アトリビュートの値を使わずに、コード・リファレンスを使って絶対パスに変換しさせることにしました。コード・リファレンスを呼び出すとき、ローカル変数 $_ はプロセス中の(この場合は a エレメントの)ノードになります。なお、第一引数に同じノード・オブジェクトが渡されるため、$_ を使わずに次のように記述することもできるようです。

       'link' => sub {
           my($node) = @_;
           URI->new_abs($node->attr('href'), $base_uri)->as_string
       }

ここで渡されるノードは HTML::Element のインスタンスで、アトリビュートを取り出すにはメソッド attr を使います。
それでは、べつやくれいさんのエントリに対応する p 要素を抽出するスクレーパーを作ってみましょう。font 要素の子テキスト・ノードに「べつやく」の文字列が含まれている p 要素を表す XPath を使いましょう。手抜きでソース・コードに漢字をハードコードすることにして、ソースコードUTF-8 で記述し、utf8 フラグを立てます。

my $entries = scraper {
    use utf8;
    process q{//td/p/font[text() =~ /べつやく/]/../../p},
        'entries[]' => $entry;
    result 'entries';
};

XML::XPathXML::XPathEngine で XPath正規表現を利用できる拡張表現を使っています。なお、いったん td 要素まで遡ってからわざわざ p 要素へ降りているのは、こうしておかないと XML::XPathEngine がエラーになるためです。
では、全部くっつけてみます。出力は YAML にすることにしました。

#!/usr/bin/perl
use strict;
use warnings;
use Web::Scraper;
use URI;
use Encode;
use YAML;

my $archive_uri = 'http://portal.nifty.com/2007';
my $base_uri;
my $entry = scraper {
    process 'p>b>font', 'date' => 'text';
    process 'p>a',
        'title' => 'text',
        'link' => sub { URI->new_abs($_->attr('href'), $base_uri)->as_string };
    process 'p>font', 'author' => 'text';
    result 'link', 'date', 'title', 'author';
};
my $entries = scraper {
    use utf8;
    process q{//td/p/font[text() =~ /べつやく/]/../../p},
        'entries[]' => $entry;
    result 'entries';
};
my $archive = [];
for my $month (qw(07 06 05)) {
    $base_uri = URI->new("$archive_uri/$month/index.htm");
    my $result = $entries->scrape($base_uri);
    push @$archive, @$result;
}
print Encode::encode('utf8', YAML::Dump($archive));

実行するとべつやくれいさんリンク集ができあがります。

---
- author: (べつやくれい)
  date: 2007.7.21
  link: http://portal.nifty.com/2007/07/21/a/
  title: 風鈴市にいってきました
- author: (べつやくれい)
  date: 2007.7.14
  link: http://portal.nifty.com/2007/07/14/a/
  title: お菓子で作るジオラマ
- author: (べつやくれい)
  date: 2007.7.7
  link: http://portal.nifty.com/2007/07/07/a/
  title: ペットボトルをボーボいわす
- author: (べつやくれい)
  date: 2007.6.23
  link: http://portal.nifty.com/2007/06/23/a/
  title: 優先席の人コレクション
- author: (べつやくれい)
  date: 2007.6.16
  link: http://portal.nifty.com/2007/06/16/a/
  title: みそあんファンクラブ
- author: (べつやくれい)
  date: 2007.6.9
  link: http://portal.nifty.com/2007/06/09/a/
  title: 鷹になりました
- author: (べつやくれい)
  date: 2007.5.26
  link: http://portal.nifty.com/2007/05/26/a/
  title: 大きな指ハブが作りたい
- author: (べつやくれい)
  date: 2007.5.19
  link: http://portal.nifty.com/2007/05/19/a/
  title: 子どものように走り回りたい
- author: (べつやくれい)
  date: 2007.5.12
  link: http://portal.nifty.com/2007/05/12/a/
  title: 声がかわりました
- author: ' (べつやくれい) '
  date: 2007.5.1
  link: http://portal.nifty.com/cs/xgoldenweek/article/070430012029/1.htm
  title: はじめての東神奈川