バイト先でスクレイピングをすることになりそうだ。
Javaでwebページの解析をしたら日が何回も沈みそうだし(というか、解析元のhtmlが汚くて読めねぇ( つД`))、言語は何でもいいらしいので、最近やたら見かけるperlのWeb::Scraper(ドキュメント)を使ってみることにした。
今日はそれのメモ。
まず、Web::Scraperに慣れるために題材はオレのソーシャルブックマーク一覧のページに右あるtagの名前と数を抜き出すことにする。
とりあえず、htmlの該当箇所の
<div id="sidebar" class="list"><div class="sidebar-inner"> <ul class="bundles"> <li class="bundle fold"><h3 class="label"><span>tags</span></h3> <ul> <li><span>3</span> <a href="/Lyo/.htaccess">.htaccess</a></li> <li><span>6</span> <a href="/Lyo/.NET">.NET</a></li> <li><span>4</span> <a href="/Lyo/2006">2006</a></li> (略)
class名がbundle foldのliに注目した。liからul, liと辿れば取得したいデータにたどり着ける。
Web::Scraperを使ってコレを表現すると以下のようになる。
use URI; use Web::Scraper; use YAML::Syck; my $delicious_tag = scraper { process 'a', tag => 'TEXT'; process 'span', bookmark_count => 'TEXT'; result qw(tag bookmark_count); }; my $delicious = scraper { process 'li.bundle > ul > li', 'tags[]' => $delicious_tag; result "tags"; }; my $res = $delicious->scrape( URI->new(shift) ); print Dump($res);
process に渡す引数がミソかねぇ。li.bundle > ul > liと対象をCSSセレクタで絞って複数取得する。(tags[]の部分。[]で複数指定。コレがないと一個しか取ってきてくれない。)
ソースで言うと
<li><span>3</span> <a href="/Lyo/.htaccess">.htaccess</a></li> <li><span>6</span> <a href="/Lyo/.NET">.NET</a></li> <li><span>4</span> <a href="/Lyo/2006">2006</a></li>
の部分。あとは複数個のliの中身の部分を$delicious の上($delicious_tag)で定義した内容で処理する。そこでaタグとspanタグに分解して、タグに囲まれてる部分を取得するような感じ。ちなみに当初はli.bundleやtags[]といった記法の意味が全然分からなかったのだが、元々このモジュールはrubyのscrAPIにインスパイアされて作られたそうなので、記法はscrAPIのドキュメントを見ろと言うことらしい。ドキュメントのsee alsoからscrAPI Cheat Sheetに飛べる。後はコレを見てがんがります。
実行
perl perl my_del.icio.us_tag.pl http://del.icio.us/Lyo
---
-
bookmark_count: 3
tag: .htaccess
-
bookmark_count: 6
tag: .NET
-
bookmark_count: 4
tag: 2006
(中略)
-
bookmark_count: "? "
tag: cloud
-
bookmark_count: "? "
tag: freq
-
bookmark_count: "? "
tag: 2
-
bookmark_count: "? "
tag: hide
<li class="bundle fold options"><h3 class="label"><span>tag options</span></h3> <ul> <li><span>» </span>view as <a href="?settagview=cloud">cloud</a> | list</li> <li><span>» </span>sort by alpha | <a href="?settagsort=freq">freq</a></li>
li.bundleと表現すると、bundleから始まるクラス名全てを拾ってしまうらしい。optionのところもtagsと同じ構造なのでクラス名をbundle foldで完全一致させなければならない。ソースを修正する。
use URI; use Web::Scraper; use YAML::Syck; my $delicious_tag = scraper { process 'a', tag => 'TEXT'; process 'span', bookmark_count => 'TEXT'; result qw(tag bookmark_count); }; my $delicious = scraper { process 'li[class="bundle fold"] > ul > li', 'tags[]' => $delicious_tag; result "tags"; }; my $res = $delicious->scrape( URI->new(shift) ); print Dump($res);
先程のソースで実行した結果をdump_listに、修正したソースで実行した結果をdump_list2に流した後、diff -u dump_list dump_list2 | lv -c
+++ dump_list3 Fri Oct 19 18:16:22 2007 @@ -3338,15 +3338,3 @@ - bookmark_count: 1 tag: "ネタ" -- - bookmark_count: "? " - tag: cloud -- - bookmark_count: "? " - tag: freq -- - bookmark_count: "? " - tag: 2 -- - bookmark_count: "? " - tag: hide
素晴らしい。余計な情報が全て消えた。
今回はソースを直に追ったが、正直コレはツライ。ブラウザがFirefoxなら、いちいち目で追わなくてもCSSセレクタを一発で表示してくれる「Firebug」か「Web Developer」を入れるべきだろう。
それにしても約20行のソースでココまで出来てしまうのがスゴイ。正に巨人の肩に乗ってるような感じ。