GoutteからみるSymfony2の使われ方

こんにちは、小川です。
今回はPHP製のスクレイピングライブラリ「Goutte」を紹介します。

Goutteの作者はSymfonyプロジェクトのリーダーを務めるFabien Potencier氏です。
Goutteの利用にはPHP 5.3が必須です。また、GoutteはSymfony2のコンポーネントやZend Frameworkの一部のライブラリを利用しています。

まずはインストールを行います。GitHubのリポジトリをクローンします。


$ git clone http://github.com/fabpot/Goutte.git

実際にスクレイピング処理を記述するscrape.phpを作成します。
Goutteは単一のPharアーカイブにまとめられています。
このPharアーカイブを読み込むだけでGoutteが利用可能です。読み込みはPHPファイルと同様、requireで行います。
(PHP: Phar - Manual)

先ほどクローンしたGoutteディレクトリの直下にgoutte.pharがあるので、これを読み込みます。


<?php
require '/path/to/Goutte/goutte.phar';

scrape.phpでgoutte.pharを読み込んだら、スクレイピングを行うためのGoutte\Clientクラスのオブジェクトを作成します。
Goutte\ClientクラスはSymfony\Component\BrowserKit\Clientクラスの子クラスにあたります。


<?php
// ...
use Goutte\Client;
$client = new Client();

Goutte\Clientクラスのrequest()メソッドでWebページの情報を取得します。

Symfony\Component\BrowserKit\ClientクラスはWebブラウザをエミュレーションするためのフレームワークとなっており、このクラス自体にリクエストを送信してレスポンスを取得する機能は実装されています。
そこで、Goutte\Clientクラスの内部でZend\Http\Clientクラスを利用して実際にリクエストを送信しています。

request()メソッドの戻り値はSymfony\Component\DomCrawler\Crawlerクラスのオブジェクトです。


<?php
// ...
$cralwer = $client->request('GET', 'http://blog.asial.co.jp/');
// echo get_class($crawler); //=> Symfony\Component\DomCrawler\Crawler

Symfony\Component\DomCrawler\CrawlerクラスはjQueryのようにCSSセレクタを用いてDOMにアクセスし、要素を取得することができます。

のトップに表示されている全ブログのタイトルを取得するのは次のようになります。シアルブログのトップに表示されている全ブログのタイトルを取得するのは次のようになります。


<?php
// ...
$crawler = $client->request('GET', 'http://blog.asial.co.jp/');
$crawler->filter('h3.entry-title')->each(function($node) {
    echo trim($node->nodeValue) . "\n";
});

filter()メソッドにCSSセレクタを指定すると、マッチする要素を取得できます。filter()メソッドの戻り値は、フィルタリングされた要素を保持するSymfony\Component\DomCrawler\Crawlerオブジェクトになります。
each()メソッドには無名関数(Closureオブジェクト)を指定します。無名関数に渡されたDOMNodeオブジェクトの値を取得しています($node->nodeValue)。

実行結果は次のようになります。


$ php scrape.php
パン作り再開です
無料で使えるメール配信アプリケーション
PHPでバイナリプログラミングその2 テキストとは何か
MacでAndroid開発 準備編
PHPでリフレクション
lsyncdを使ったディレクトリ同期の制限について
UMLを描こう - Vol.2 シーケンス図
sshでポートフォワード
もうすぐ健康診断があるんだ・・・

これでスクレイピング完了です。非常に簡単ですね。
ただ、ここまでやっておいてなんですが、今回伝えたかったことはGoutteの紹介ではありません。

Goutteはそれ自体が特別な機能を持っているわけではなく、SymfonyのBrowserKitとZend FrameworkのHttpClientを組み合わせただけです。
結局のところGoutteとは、Symfony2の新たな使い方を示したライブラリといってもよいでしょう。

symfony 1.xではFormやRoutingなど、symfonyの一部を「サブフレームワーク」とみなしていました。
Symfony2になり、単独でも動作するサブフレームワークの類が「Symfony Components」として、より明確に抜き出されました。

例えばDoctrine2では、SymfonyのConsoleコンポーネントを用いてコマンドラインの機能を提供しています。
Symfony2がZend FrameworkのLoggerなどを利用しているように、Symfony2の一部を他のフレームワークやライブラリから利用する、ということも今後増えていくでしょう。

GoutteであればPharアーカイブにしていますし、Doctrineでは利用しているSymfony Componentsを部分的にリポジトリに含めています。
Symfony Componentsを使ううえで、必ずしもSymfony2を別途インストールする必要はありません。
仮に機能が多少古くても、ライブラリにとって十分であるならば常に最新バージョンに追随する必要もないでしょう。

世の中には「車輪の再発明(reinventing the wheel)」という言葉があります。
Symfony2ではZend Frameworkの他にも、ORMにDoctrineやPropel、テスティングにPHPUnitを利用しています。

既存ライブラリを活用する他にも、特定のライブラリを利用したいと思ったときに組み込みやすい仕組みにもなっています。
テンプレートエンジンを抽象的に扱うTemplatingコンポーネントのおかげでTwigも簡単に組み込めます。
特定の具象クラスに依存はせず、様々な箇所で"インターフェイス"に依存するようなつくりになっています。
そして、何といってもDIコンテナです。クラスの管理が非常にスマートに行えます。

ここまでやったらもうJavaでいいじゃん!とか思ったりもしますが、環境構築の手軽さやvar_dump()の存在を考えるとやっぱりPHPって楽でいいですよね。
(参考: モダンなPHPの開発環境の構築方法 - 肉とご飯と甘いもの @ sotarok)