2007年05月26日
HTMLをXML化してDOMやXPathで操作するWebスクレイピング用PHPクラス
SimpleXML+HTMLParser or Tidy+HTTP_Request+Cache_Liteの組み合わせで、リモートサイトから取得したHTMLソースを整形して、SimpleXMLオブジェクトに変換するライブラリでございます。サーバサイドはもとよりクライアントサイドのAjaxでも、DOMやXPathなどを用いたオブジェクト操作によるScrapingが可能になります。要SimpleXMLなのでPHP5専用(もっとも、SimpleXMLをDOM XMLに置き換えればPHP4でも同じようなことはできますが)。
ええっと手っ取り早く、以下にアクセスしてフォームに適当なURLを入力してみてください。
下図のようにURLがダラダラとリストされれば正解。

ここでリストされるURLは、ご覧いただければおわかりの通り、入力したURLのウェブページ内にあるA要素のHREF属性値なわけですが、ポイントはこれを、responseXMLからDOMで取得していること。つまりXMLHttpRequestのリクエスト先は、well-formedなXMLを返してるということでございます。あと、HREF属性値はすべてフルパスに変換されてたりもします。
そのXMLHttpRequestのリクエスト先であるPHPアプリのソースは以下の通り。
<?php
if (!isset($_GET['url']) or empty($_GET['url'])) {
header("$_SERVER[SERVER_PROTOCOL] 400 Bad Request");
header('Content-Type: text/plain;charset=UTF-8');
exit('The URL is not specified.');
} else {
require_once 'HTMLScraping.class.php';
$s = new HTMLScraping;
try {
$xml = $s->getXmlObject($_GET['url']);
} catch (Exception $e) {
header("$_SERVER[SERVER_PROTOCOL] 400 Bad Request");
header('Content-Type: text/plain;charset=UTF-8');
exit($e->getMessage());
}
$s->convertPath($xml, array('a' => 'href'));
header('Content-Type: application/xml;charset=UTF-8');
exit($xml->asXML());
}
?>
HTMLScraping->getXmlObject()は、第1パラメータのURLで取得したHTMLソースから生成したSimpleXMLオブジェクトを返します(エラーの際には例外を投げますんで、上の例のようにtry~catchしてやってください)。
ちなみに、キャッシュ処理を行う場合、まずは以下のように、コンストラクタの第1パラメータにキャッシュファイルを保存するディレクトリを指定しておきます(このディレクトリはもちろん、PHPアプリに対するread/writeパーミッションが必要です)。
$s = new HTMLScraping('/tmp/');
で、getXmlObject()の第2パラメータに秒数を指定すると、URLのレスポンスが指定した秒数キャッシュされます。
$xml = $s->getXmlObject($_GET['url'], 3600);
その他のオプションパラメータやその他のメソッドの説明、及び配布アーカイブのダウンロードなどは、説明ページへどーぞ。
応用例:HTMLをScrapingしてRSSフィードを生成する
さてこれだけでは面白くないので、これの応用例としてHTMLからRSSフィードを生成するということをやってみました。と言ってもHTMLの内容は多様で一律に処理できるわけはないので、共通処理をまとめたabstractなクラスを用意し、この継承クラスでオーバーライドするメソッド内で、HTMLScraping->getXmlObject()で取得したSimpleXMLオブジェクトから必要なデータを抽出していただこうという寸法でございます。
注意:これはあくまで応用例として用意したもので、実用に供されることを意図したものではありません。
配布アーカイブ中のexamplesディレクトリにあるtest_feed.phpはこのサンプルアプリケーションで、同ディレクトリ内にあるtest_feed.htmlからリストアイテム要素を取り出し、フィードを構築して出力します(このアプリケーションはキャッシュ機能を使用しますので、実際に自分の環境で実行する場合は、コンストラクタのパラメータで指定しているキャッシュファイルの保存先ディレクトリ名を、実際に利用可能なものに変更してください)。
HTMLToFeed.class.phpは、test_feed.php内で継承しているabstractクラスHTMLToFeedを定義しています。HTMLToFeed->getFeed()は、DOMで構築したフィードを出力しますが、キャッシュ処理が有効な場合は、フィードの出力をキャッシュし、出力にLast-Modified/Etagヘッダを含めて、If-Modified-Since/If-None-Matchによる条件付きリクエストに応答します。
test_feed.phpでは、アクセス先のURLが単一(しかもコンテンツが変更されない)であるため、出力のみをキャッシュしていますが、複数のURLにアクセスしてその内容をマージするような場合、URLごとに、その更新頻度に応じてキャッシュの有効期間を設定し、それに加えて出力もキャッシュすることで、アプリケーションのパフォーマンスを向上させることができるかもしれません。
DisclaimrBETA
まあこれは、こんな感じでこんなことができますよというサンプルであって、これを広く使ってもらおうなんていう気は毛頭なく。仕様も自分の好みでやらかしてるので、あんまし汎用的ではないかもしれず。そもそもライブラリの組み合わせからして、別段この組み合わせに限ったわけでも、もちろんありません。
特に、Tidyが利用できればこんなことはもっとお手軽に(リソースの消費も少なく高速に)できちゃうわけで。しかし、HTMLParserの趣旨にも書いたように、現在の一般的なレンタルサーバのPHP環境ではこれは殆ど利用できない筈なので、そういう場合には利用価値があるやもしれませんが(このライブラリでは、Tidyが利用可能ならそちらを、そうでなければ(自動的に)その代替にHTMLParserを利用するようになってたりはします)、そんな感じのツクリなので、気に入らないところがあれば、しょせん既存のライブラリの組み合わせなので、わざわざオーバーライドするよりは自分好みの仕様で作り直しちゃうことをお勧め申し上げる次第でございます。
Category: ウェブ制作
Posted 2007年05月26日 00:25
トラックバック
このエントリーのトラックバックURL:
http://www.rcdtokyo.com/mt/mt-rcdtokyo5428-tb.cgi/820