2007.05.12 Saturday
3年前にこの本に出会って、僕はスクレーピングの虜になった。
おかげで正規表現をたくさん書く機会が得られたし、CPANのモジュールともたくさん触れ合うことができたし、さらに自分自身でも構造化されたHTMLを書く習慣も身についた。
ただ、やっぱり、スクレーピングは簡単ではない部分もたくさんあると思う。文字コードについても意識しなければならないし、なにより正規表現で必要な部分を抽出することそのものが結構骨のいる作業だ。HTML::TreeBuilder を使った方がいいのか、それとも正規表現だけでやったほうが楽なのか。そんなことを考慮しながらコーディングして行く。そこがスクレーピングの楽しいところでもあるのだが、そこがスクレーピングの敷居を高いものにしていることも否めない。
そんな、愛らしくもあり憎たらしくもあるスクレーピングを簡単にやってしまうのがこのモジュール、Web::Scraper だ。
今回はこのWeb::Scraper を簡単に使うための初歩の初歩を、Web::ScaperのPodドキュメントを補う形で自分の理解をまとめた。
Web::Scraper を使ってのスクレーピングの流れはとても簡単だ。
1. どんな条件で抽出するかを記述したscraper(コードリファレンス?)を書く。
2. それを使って、scrape に対象となるURLを渡して実行する。
で、早速、具体例。具体例はPodのSynopsis。
(赤字部分は便宜上追加したところ)
Web::Scraper Synopsysより
紹介されているSynopsis では何が取得できるのかわからないので、ちょっとだけコードを追加して、取得した情報を画面に表示できるようにした。
(ここでは結果をDumpするのにYAML::Syck を使っているが、もちろんData::Dumperなどで代用もできる。)
(参考)取得結果
このSynopsisはシンプルだけれども、コールバックが使えることも示唆していて、素晴らしいサンプルだと思う。
このコードがどういうことをやっているのか、上から見てみた。
「class属性がensで始まる<h3>エレメントの内側にある<a>エレメントについて」、
「TEXTノードをdescriptionというkeyのハッシュに」、
「href属性をurlというkeyのハッシュにいれなさい」、
という意味だ。あとはもうほとんど一緒。
「class属性がebcPrで始まる<td>エレメントの内側にある<span>エレメントについて」、
「TEXTノードをpriceというkeyのハッシュにいれなさい」
続いて、
「class属性がebPictureで始まる<div>エレメントの内側にある<a>エレメントの内側にある<img>エレメントについて」、
「src属性をimgというkeyのハッシュにいれなさい」
で、最後にそれを結果として返すようにするだけ。
実際に、http://search.ebay.com/apple-ipod-nano_W0QQssPageNameZWLRSにアクセスして、そのHTMLのソースと見比べるとよくわかる(HTMLの掲載は割愛)。
このサンプルコードには続きがあって、もう1つのscraper が定義されている。
実際にscrape が実行されているのはこちらのようだ。
この部分が、scraper にコールバック関数を使えることを示している。
「class属性がebItemlistで始まる<table>エレメントの内側にある、class属性がsingleで始まる<tr>エレメントについて」、
「$ebay_auction というコードリファレンスで指定されるメソッドを実行して、その結果をauctions という配列にいれなさい」
いまだに、テーブルコーディングされているサイトも多いし、div でCSSコーディングされているサイトであっても、ブロックが入れ子構造になっているサイトは多いと思う。
そういうサイトからデータを抽出するときは、まず、どのブロックからデータを取得するのかを大まかに指定してしまって、その中で細かい情報を取得するという段階を踏むと、スクレーピングしやすい。このSynopsis でコールバックを使っているのは、まさにそういう方法を見せるためだと思う。
まず、どのブロックからデータを取得するのか。ここでは、<table class="ebItemlist">の内側からデータを取るよ、ということを$ebay で指定して、実際にそのブロックの中のどの部分をどのように抽出するのかという細かいところを$ebay_auction で指定しているのだろう。
このコードの結果と、このサイトのHTMLソースを見比べるとよくわかった。
また、実際に試しているひともたくさんいて、その人たちの解説や素敵なサンプルもためになった。
- Web::Scraper | naoyaのはてなダイアリー
- [Perl]Web::Scraper使ってみた | Unknown::Programming
- Web::Scraper 勝手に添削 | Bulknews::Subtech
おかげで正規表現をたくさん書く機会が得られたし、CPANのモジュールともたくさん触れ合うことができたし、さらに自分自身でも構造化されたHTMLを書く習慣も身についた。
ただ、やっぱり、スクレーピングは簡単ではない部分もたくさんあると思う。文字コードについても意識しなければならないし、なにより正規表現で必要な部分を抽出することそのものが結構骨のいる作業だ。HTML::TreeBuilder を使った方がいいのか、それとも正規表現だけでやったほうが楽なのか。そんなことを考慮しながらコーディングして行く。そこがスクレーピングの楽しいところでもあるのだが、そこがスクレーピングの敷居を高いものにしていることも否めない。
そんな、愛らしくもあり憎たらしくもあるスクレーピングを簡単にやってしまうのがこのモジュール、Web::Scraper だ。
今回はこのWeb::Scraper を簡単に使うための初歩の初歩を、Web::ScaperのPodドキュメントを補う形で自分の理解をまとめた。
Web::Scraper を使ってのスクレーピングの流れはとても簡単だ。
1. どんな条件で抽出するかを記述したscraper(コードリファレンス?)を書く。
2. それを使って、scrape に対象となるURLを渡して実行する。
my $scraper = scraper { [抽出条件] };
$scraper->scrape( URI->new([対象サイトURL]) );こんなイメージ。で、早速、具体例。具体例はPodのSynopsis。
(赤字部分は便宜上追加したところ)
Web::Scraper Synopsysより
use URI;
use Web::Scraper;
use YAML::Syck;
my $ebay_auction = scraper {
process "h3.ens>a",
description => 'TEXT',
url => '@href';
process "td.ebcPr>span", price => "TEXT";
process "div.ebPicture >a>img", image => '@src';
result 'description', 'url', 'price', 'image';
};
my $ebay = scraper {
process "table.ebItemlist tr.single",
"auctions[]" => $ebay_auction;
result 'auctions';
};
my $res = $ebay->scrape( URI->new("http://search.ebay.com/apple-ipod-nano_W0QQssPageNameZWLRS") );
print Dump($res);
紹介されているSynopsis では何が取得できるのかわからないので、ちょっとだけコードを追加して、取得した情報を画面に表示できるようにした。
(ここでは結果をDumpするのにYAML::Syck を使っているが、もちろんData::Dumperなどで代用もできる。)
(参考)取得結果
〜前略〜
-
description: Stereo Earphone Headphone For Apple iPod nano Video W
image: http://thumbs.ebaystatic.com/pict/1701110335328080_1.jpg
price: $2.46
url: http://cgi.ebay.com/Stereo-Earphone-Headphone-For-Apple-iPod-nano-Video-W_W0QQitemZ170111033532QQihZ007QQcategoryZ111693QQrdZ1QQcmdZViewItem
-
description: For iPod Nano 1st G Apple iPod Battery+Replacement Kit
image: ~
price: $4.98
url: http://cgi.ebay.com/For-iPod-Nano-1st-G-Apple-iPod-Battery-Replacement-Kit_W0QQitemZ250113923030QQihZ015QQcategoryZ118261QQrdZ1QQcmdZViewItem
-
description: For iPod Nano 1st 2nd Gen Sport Arm Band ~ Apple Green
image: ~
price: $2.98
url: http://cgi.ebay.com/For-iPod-Nano-1st-2nd-Gen-Sport-Arm-Band-Apple-Green_W0QQitemZ250113923033QQihZ015QQcategoryZ56170QQrdZ1QQcmdZViewItem
〜後略〜
このSynopsisはシンプルだけれども、コールバックが使えることも示唆していて、素晴らしいサンプルだと思う。
このコードがどういうことをやっているのか、上から見てみた。
process "h3.ens>a",これは
description => 'TEXT',
url => '@href';
「class属性がensで始まる<h3>エレメントの内側にある<a>エレメントについて」、
「TEXTノードをdescriptionというkeyのハッシュに」、
「href属性をurlというkeyのハッシュにいれなさい」、
という意味だ。あとはもうほとんど一緒。
「class属性がebcPrで始まる<td>エレメントの内側にある<span>エレメントについて」、
「TEXTノードをpriceというkeyのハッシュにいれなさい」
続いて、
「class属性がebPictureで始まる<div>エレメントの内側にある<a>エレメントの内側にある<img>エレメントについて」、
「src属性をimgというkeyのハッシュにいれなさい」
で、最後にそれを結果として返すようにするだけ。
result 'description', 'url', 'price', 'image';
実際に、http://search.ebay.com/apple-ipod-nano_W0QQssPageNameZWLRSにアクセスして、そのHTMLのソースと見比べるとよくわかる(HTMLの掲載は割愛)。
このサンプルコードには続きがあって、もう1つのscraper が定義されている。
実際にscrape が実行されているのはこちらのようだ。
この部分が、scraper にコールバック関数を使えることを示している。
process "table.ebItemlist tr.single",この部分はこういう風に解釈できる。
"auctions[]" => $ebay_auction;
result 'auctions';
「class属性がebItemlistで始まる<table>エレメントの内側にある、class属性がsingleで始まる<tr>エレメントについて」、
「$ebay_auction というコードリファレンスで指定されるメソッドを実行して、その結果をauctions という配列にいれなさい」
そういうサイトからデータを抽出するときは、まず、どのブロックからデータを取得するのかを大まかに指定してしまって、その中で細かい情報を取得するという段階を踏むと、スクレーピングしやすい。このSynopsis でコールバックを使っているのは、まさにそういう方法を見せるためだと思う。
まず、どのブロックからデータを取得するのか。ここでは、<table class="ebItemlist">の内側からデータを取るよ、ということを$ebay で指定して、実際にそのブロックの中のどの部分をどのように抽出するのかという細かいところを$ebay_auction で指定しているのだろう。
このコードの結果と、このサイトのHTMLソースを見比べるとよくわかった。
また、実際に試しているひともたくさんいて、その人たちの解説や素敵なサンプルもためになった。
- Web::Scraper | naoyaのはてなダイアリー
- [Perl]Web::Scraper使ってみた | Unknown::Programming
- Web::Scraper 勝手に添削 | Bulknews::Subtech