2008.01.16 Wednesday
例えばこんなHTMLからニュースの一覧を取得することを考えよう。
例えば、こんな感じで書いてみたらどうだろうか。
ここはやっぱり、こんな形のデータ構造として取得したいところだ。
1. result[]という配列でul.news>liの中身をおおまかに受け取ること。
2. それに対して、aタグのTEXT要素やhref属性の値を取得していること。(コードリファレンスで)
3. コードリファレンスの中ののtitleやlinkが複数形(配列)になっていないこと。
ul.news>li をresult[]という配列で受け取る形になっていて、その中身にtitleやlinkがあるのだから、そのresultの要素ひとつひとつに対しては、titleやlinkはひとつずつしかないからだ。
というわけで、以前に僕が書いたWeb::Scraper 使い方(超入門)にはちょっと嘘が書いてある。
ここの部分は間違いだ。取得したいデータを2次元以上のデータ構造として取得したい場合に、コードリファレンスを使えばいいのだ。
コメントで指摘してくれたaoiさん。
ありがとうございました。
2008/01/17 10:05 追記
miyagawaさんからコメントで補足を頂きました。
$item とう一時変数を用意しなくても下記のようにすっきりと書けます。
これで、入れ子入れ子の繰り返しで、複雑なデータ構造もバチッと取れますね。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">ニュースのタイトルの一覧を取得するとしたら、やっぱりタイトルは複数あるので配列で受け取りたいところだ。そこで、'titles[]'みたいな形になると想像できる。で、こんなコードになる。
<html lang="ja" xml:lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>サッカーニュース</title>
<body>
<ul class="news">
<li>
<a href="http://sports.livedoor.com/article/vender-15.html">C・ロナウドが休日返上宣言!</a>
</li>
<li>
<a href="http://sportsnavi.yahoo.co.jp/soccer/index.html">イタリア代表のドナドーニ監督「アイルランドを甘く見てはいない」</a>
</li>
<li>
<a href="http://sportsnavi.yahoo.co.jp/soccer/index.html">バルセロナが前回王者セビージャを下す=スペイン国王杯</a>
</li>
<li>
<a href="http://sportsnavi.yahoo.co.jp/soccer/index.html">ユベントス奮闘、5−3でエンポリを下す=イタリア杯</a>
</li>
</ul>
</body>
</html>
use strict;結果(以下、YAML形式でDumpする)
use Web::Scraper;
use URI;
my $uri = URI->new("http://localhost/~tobe/news_sample.html");
my $scraper = scraper {
process "ul.news>li>a" ,'titles[]' => 'TEXT';
};
my $result = $scraper->scrape($uri);
---上々の出来だ。今度は、それぞれのリンク先を取得したいと思う。
titles:
- C・ロナウドが休日返上宣言!
- イタリア代表のドナドーニ監督「アイルランドを甘く見てはいない」
- バルセロナが前回王者セビージャを下す=スペイン国王杯
- ユベントス奮闘、5−3でエンポリを下す=イタリア杯
use strict;結果
use Web::Scraper;
use URI;
my $uri = URI->new("http://localhost/~tobe/news_sample.html");
my $scraper = scraper {
process "ul.news>li>a" ,'links[]' => '@href';
};
my $result = $scraper->scrape($uri);
---全く問題ない。ところが、この考えの延長でいくと、タイトルとリンクの組み合わせを取得したいときに無理が生じる。
href:
- !!perl/scalar:URI::http http://sports.livedoor.com/article/vender-15.html
- !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
- !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
- !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
例えば、こんな感じで書いてみたらどうだろうか。
use strict;結果
use Web::Scraper;
use URI;
my $uri = URI->new("http://localhost/~tobe/news_sample.html");
my $scraper = scraper {
process "ul.news>li>a" ,'titles[]' => 'TEXT', 'links[]' => '@href';
};
my $result = $scraper->scrape($uri);
---確かに、取得できているんだけど、これではちょっとうまくない。1次元の別々の配列で、添字が等しければ...なんてのはデータ構造としてあんまりよろしくない。
links:
- !!perl/scalar:URI::http http://sports.livedoor.com/article/vender-15.html
- !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
- !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
- !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
titles:
- C・ロナウドが休日返上宣言!
- イタリア代表のドナドーニ監督「アイルランドを甘く見てはいない」
- バルセロナが前回王者セビージャを下す=スペイン国王杯
- ユベントス奮闘、5−3でエンポリを下す=イタリア杯
ここはやっぱり、こんな形のデータ構造として取得したいところだ。
---そこで重要なのがコードリファレンスを使ったやり方だ。これをマスターしなければ、Web::Scraperは使いこなせない。
result:
- link: !!perl/scalar:URI::http http://sports.livedoor.com/article/vender-15.html
title: C・ロナウドが休日返上宣言!
- link: !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
title: イタリア代表のドナドーニ監督「アイルランドを甘く見てはいない」
- link: !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
title: バルセロナが前回王者セビージャを下す=スペイン国王杯
- link: !!perl/scalar:URI::http http://sportsnavi.yahoo.co.jp/soccer/index.html
title: ユベントス奮闘、5−3でエンポリを下す=イタリア杯
use strict;ここでのポイント
use Web::Scraper;
use URI;
my $uri = URI->new("http://localhost/~tobe/news_sample.html");
my $items = scraper {
process "a" ,'title' => 'TEXT', 'link' => '@href';
result 'item';
};
my $scraper = scraper {
process "ul.news>li", 'result[]' => $items;
};
my $result = $scraper->scrape($uri);
1. result[]という配列でul.news>liの中身をおおまかに受け取ること。
2. それに対して、aタグのTEXT要素やhref属性の値を取得していること。(コードリファレンスで)
3. コードリファレンスの中ののtitleやlinkが複数形(配列)になっていないこと。
ul.news>li をresult[]という配列で受け取る形になっていて、その中身にtitleやlinkがあるのだから、そのresultの要素ひとつひとつに対しては、titleやlinkはひとつずつしかないからだ。
というわけで、以前に僕が書いたWeb::Scraper 使い方(超入門)にはちょっと嘘が書いてある。
いまだに、テーブルコーディングされているサイトも多いし、div でCSSコーディングされているサイトであっても、ブロックが入れ子構造になっているサイトは多いと思う。
そういうサイトからデータを抽出するときは、まず、どのブロックからデータを取得するのかを大まかに指定してしまって、その中で細かい情報を取得するという段階を踏むと、スクレーピングしやすい。このSynopsis でコールバックを使っているのは、まさにそういう方法を見せるためだと思う。
まず、どのブロックからデータを取得するのか。ここでは、<table class="ebItemlist">の内側からデータを取るよ、ということを$ebay で指定して、実際にそのブロックの中のどの部分をどのように抽出するのかという細かいところを$ebay_auction で指定しているのだろう。
ここの部分は間違いだ。取得したいデータを2次元以上のデータ構造として取得したい場合に、コードリファレンスを使えばいいのだ。
コメントで指摘してくれたaoiさん。
ありがとうございました。
2008/01/17 10:05 追記
miyagawaさんからコメントで補足を頂きました。
$item とう一時変数を用意しなくても下記のようにすっきりと書けます。
use strict;
use Web::Scraper;
use URI;
my $uri = URI->new("http://localhost/~tobe/news_sample.html");
my $scraper = scraper {
process "ul.news>li>a",
'result[]' => { 'title' => 'TEXT', 'link' => '@href' };
};
my $result = $scraper->scrape($uri);
これで、入れ子入れ子の繰り返しで、複雑なデータ構造もバチッと取れますね。