WebスクレイピングライブラリGoutteで遊んでみる
新年あけましておめでとうございます。今年もボチボチやっていきます。
本稿ではPHP製のWebスクレイピングライブラリGoutteを紹介します。
Goutte(グット)とは
Goutteは必要十分な機能を持ったWebスクレイピングライブラリです。そもそもWebスクレイピングというのは、外部Webページから必要なデータを取ってくるくらいの意味です。つまり、GoutteはWebスクレイピングを簡単に行う道具だと考えればいいでしょう。
具体的には、GoutteはWebクローラとHTMLパーサを組み合わせたようなものです。Cookieやフォームの扱いなどWebブラウザとしての機能は一通り揃っていますし、CSS風の要素指定もできるなど、機能面では他のライブラリと遜色ないように感じます。
さらに僕個人がGoutteに期待している点は、安定性とロングサポートです。Goutteは主要機能をSymfony2およびZendFrameworkのコンポーネントで実現しており、Goutte自身はそれらをつなぎ合わせているだけです。実際、Goutte本体のコードは300行くらいであり、非常に「筋がいい」ハックであると感じます。
Goutte自体はまだ正式リリースはされておらず、GitHubのプロジェクトページしかありません。とはいえ、最近までPull Requestを取り込んだりしていますので、作者のFabienが飽きたわけではなさそうです。既に実用レベルだと思いますので、頃合いを見て正式リリースしてもらいたいものです。
ちなみに、Goutteというのはフランス語でしずく、または水滴といった意味のようです。
Goutteの特徴
僕がGoutteで特徴的だと思うのは次の3点です。
CSSセレクタの存在
Goutteでは、CSSセレクタをXPathに変換するクラス(CssSelector)を利用しています。これにより、Webスクレイピング時の要素指定がCSSセレクタで行えますので、XPathが苦手な人や縁遠い人でも安心です。
<?php /*省略*/ $timestampStr = $crawler->filter('div.paragraph:first-child span#timestamp')->text();
このクラスはSymfony2が提供しているもので、PythonのlxmlというライブラリをPHPにportしたものです(出典:「Parsing XML documents with CSS selectors - Fabien Potencier」)。平易なCSSセレクタを書いている限り特に問題なく使えると思いますが、詳細な仕様が知りたい場合はlxmlのマニュアルを参照する必要があります。
ブラウザ機能の充実
Goutteではブラウザ機能の実現に次のようなクラスを利用しています。
- Zend_Http_Client
- DomCrawler
- BrowserKit
これらの機能を組み合わせることで、受け取ったCookieを次のリクエストで自動的に送信したり、特定のリンクをクリックしたり、フォームに適切な値を詰めてPOSTしたりといった、ブラウザが行っている機能が不足なく実現できます。
そもそも、上記クラスのうちHTTP通信以外の部分はSymfony2の機能テスト(functional test)で実績のあるものです。Webアプリケーションの機能テストでは、ブラウザのフリをしてアプリケーションにアクセスし、何ページか遷移したり、出力されたHTMLが期待通りかチェックしたりします。そうした出自を考えれば、ブラウザとしての機能が揃っているのは当然だとも言えます。
本体コードの短さ
既に紹介した通り、Goutte自体はコメント込みで300行ほどの非常に小さいライブラリです。大半の機能についてSymfony2とZend Frameworkのコンポーネントをつなぎあわせて利用しています。
Symfony2やZend Frameworkのユーザ数の多さから、そうした外部コンポーネントの品質は高く、サポート期間も長いと予想されます。また、Goutte独自で実装している部分は非常に小さいので、独自実装部分のバグの少なさやメンテナンスの容易さといったメリットも期待できます。つまり、安定性や長い保守期間が重要な場合、Goutteは良い選択肢と言えるでしょう。
別の見方をすると、コンポーネント性の高いライブラリを組み合わせれば中規模程度のツールが簡単に作れることをGoutteは示しています。300行のコードが書けないプログラマはいないと思いますので、我々に夢を与えてくれるという意味でも素晴らしいプロジェクトだと思います。
Goutteのインストール
PHP5.3以降を用意したら、あとはpharファイル1個を好きなディレクトリに設置するだけです。下記URLをファイルとしてセーブして、goutte.pharという名前にしてください。
これをPHPスクリプトと同じディレクトリに置けばインストール完了です。もう少し工夫したい人やgitコマンドを使いたい人は適宜なんとかしてください。
Goutteの実行例
Webスクレイピングのサンプルとして有名な、はてなキーワード「紺野あさ美」から数個のテキストを抜き出してみましょう。
<?php require __DIR__.'/goutte.phar'; use Goutte\Client; $client = new Client(); $crawler = $client->request('GET', 'http://d.hatena.ne.jp/keyword/%BA%B0%CC%EE%A4%A2%A4%B5%C8%FE'); list(list($title, $url)) = $crawler->filter('div.keyword-container a.title')->extract(array('_text', 'href')); $furigana = $crawler->filter('div.keyword-container span.furigana')->text(); var_dump($title, $url, $furigana);
上記を実行すると次の結果が得られます。
string(15) "紺野あさ美" string(39) "/keyword/%BA%B0%CC%EE%A4%A2%A4%B5%C8%FE" string(18) "こんのあさみ"
Goutteクイックリファレンス
Goutteを使う上で必要な情報が分散しすぎていると感じるので、以下に代表的な機能をまとめてみました。
HTTPリクエストに関する設定
Goutte\Clientのコンストラクタ第一引数は、Zend_Http_Clientに対する設定パラメータになっています。ですから、例えばユーザーエージェントを変更したい場合は次のコードで実現できます。
<?php require __DIR__.'/goutte.phar'; use Goutte\Client; $config = array('useragent' => 'MyRobot/1.1') $client = new Client($config); $crawler = $client->request('GET', 'http://example.com/');
また、デフォルトの設定値のうち、Zend_Http_Clientのデフォルト値と異なっているものは以下の4つです。
- リダイレクトをたどる最大数(maxredirects): 0(=たどらない)
- 接続タイムアウト秒数(timeout): 30秒
- クッキー値をURLエンコードするかどうか(encodecookies): false(しない)
- ユーザーエージェント(useragent): "Symfony2 BrowserKit"
その他の設定パラメータについてはZend_Http_Clientの公式ドキュメントをご覧ください。僕は試していませんが、HTTP proxyの利用・クライアント証明書の利用・Zend_Http_Client の接続アダプタの変更などが可能なはずです。
HTTPリクエストに関するメソッド
独自のリクエストヘッダを設定したい場合はsetHeaderメソッドが利用できます。
<?php (略) $client = new Client(); $client->setHeader('X-Nantoka-Id', 'abcd0123); $crawler = $client->request('GET', 'http://example.com/');
また、Basic認証を利用する場合はsetAuthメソッドが利用できます。
<?php (略) $client = new Client(); $client->setAuth('id', 'password'); $crawler = $client->request('GET', 'http://example.com/');
ページ遷移に関するメソッド
Goutte\ClientはSymfony2のBrowserKit\Clientを継承しており、次のメソッドが利用できます。
followRedirects($followRedirect) | 自動的にリダイレクトするかどうか設定する(デフォルトでは自動でリダイレクトする) |
click($link) | リンクをクリックする |
submit($form, $values)) | フォームを送信する |
request($method, $uri, $parameters, $files, $server, $content, $changeHistory) | HTTPリクエストを行う |
back() | ブラウザの履歴を利用して前のページに戻る |
forward() | ブラウザの履歴を利用して次のページに進む |
reload() | 今のページをリロードする |
followRedirect() | リダイレクト先に遷移する |
requestメソッドの例を示します。
<?php (略) $crawler = $client->request('GET', 'http://www.symfony-project.org/');
詳しくはSymfony2ドキュメントの機能テストの説明をご覧ください。
DOM操作に関するメソッド
Clientのrequest,click,submitの各メソッドの返り値はSymfony2のDomCrawler\Crawlerクラスのオブジェクトです。このクラスのメソッドを利用して、HTMLから情報を取り出したり、リンクやフォームのオブジェクトを取り出したりすることができます。
以下はノードの絞り込みに利用できるメソッドです。
filter('h1') | CSSセレクタにマッチするノード |
filterXpath('h1') | XPath式にマッチするノード |
eq(1) | 指定したインデックスのノード |
first() | 最初のノード |
last() | 最後のノード |
siblings() | 兄弟のノード |
nextAll() | 後の兄弟ノード |
previousAll() | 前の兄弟ノード |
parents() | 親ノード |
children() | 子ノード |
reduce($lambda) | callableがfalseを返さないノード |
selectLink($value) | 指定されたテキストを含むリンクすべてを選択 |
selectButton($value) | 指定されたテキストを含むボタンすべてを選択 |
以下は情報の抽出に利用できるメソッドです。
attr($attribute) | 最初のノードの、指定した属性の値を返す |
text() | 最初のテキストノードの値を返す |
extract($attributes) | すべてのノードから、配列で指定した属性の値を抽出する(_textはテキストノードの値の意味) |
以下はリンクやフォームに対応するオブジェクトを取得するメソッドです。Clientのclickメソッドやsubmitメソッドの引数として利用できます。
form() | 最初のノードが含まれているフォームに対応するFormオブジェクトを返す |
link() | 最初のノードに対応するLinkオブジェクトを返す |
以下はリンクをクリックする例です。
<?php (略) $link = $crawler->selectLink('Plugins')->link(); $crawler = $client->click($link);
以下はフォームに送信する例です。
<?php (略) $form = $crawler->selectButton('sign in')->form(); $crawler = $client->submit($form, array('signin[username]' => 'fabien', 'signin[password]' => 'xxxxxx'));
Symfony2ドキュメントにCrawlerについての説明がありますので、あわせてご覧ください。
参考ページ
- fabpot/Goutte - GitHub
- Zend Framework: Documentation: 導入 - Zend Framework Manual
- テスト | Symfony2日本語ドキュメント
- Parsing XML documents with CSS selectors - Fabien Potencier
- lxml.cssselect
- Introducing four new PHP 5.3 components and Goutte, a simple web scraper « php|architect – The site for PHP professionals
- GoutteからみるSymfony2の使われ方 : アシアルブログ
まとめ
コード量の少なさと十分な実用性を両立しているWebスクレイピングライブラリGoutteを紹介しました。僕が以前送ったPull Requestの内容が反映されたこともあり、現時点では僕にとって不満のないツールです。今回分散している情報をまとめたことで、ユーザーが増えたらいいなあと考えています。
一方で、僕自身はあまりWebスクレイピングをする方ではないので、Goutteに肩入れしすぎかもしれません。「この機能が足りないのが惜しい」といった点がもしあれば教えてください。たとえば、Diggin*1はrobots.txtの解釈をしてくれるようですが、Goutteにはそんな機能はありません。