Hatena::ブログ(Diary)

hnwの日記 このページをアンテナに追加 RSSフィード

[プロフィール]
 | 

2012年1月15日(日) 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についての説明がありますので、あわせてご覧ください。


参考ページ


まとめ

コード量の少なさと十分な実用性を両立しているWebスクレイピングライブラリGoutteを紹介しました。僕が以前送ったPull Requestの内容が反映されたこともあり、現時点では僕にとって不満のないツールです。今回分散している情報をまとめたことで、ユーザーが増えたらいいなあと考えています。


一方で、僕自身はあまりWebスクレイピングをする方ではないので、Goutteに肩入れしすぎかもしれません。「この機能が足りないのが惜しい」といった点がもしあれば教えてください。たとえば、Diggin*1robots.txtの解釈をしてくれるようですが、Goutteにはそんな機能はありません。

dondon 2013/02/07 03:08 こちらの記事のおかげで、やりたかったことが実現できました。
ありがとうございます!

デンジャラスデンジャラス 2014/05/08 01:44 こちらの記事を参考にGoutteを導入しました。
ところがはてなキーワードから紺野あさ美さんを抽出するコードが上手くいかなく、かなり悪戦苦闘しました。
結局原因はこちらに掲載されているコードでもなく、僕のGoutte導入が失敗したからでもなく、単にはてなキーワード側でHTML5対応のため?の仕様変更?のようなものがあったからでした。
僕と同じようなPHP初心者の方のために、紺野あさ美さん抽出を成功させるための方法を書いておきます。
<?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('section.keyword-container a.title')->extract(array('_text', 'href'));
$furigana = $crawler->filter('section.keyword-container rt.ruby')->text();

var_dump($title, $url, $furigana);

具体的には、div.keyword-containerがsectionに。
span.furiganaがrt.rubyに変更されたようです。

To_aru_UserTo_aru_User 2014/11/30 23:21 「useragent」ではなく「User-Agent」では?
http://designhack.slashlab.net/php-note-for-scraping-html-xml-with-goutte/

hnwhnw 2014/12/01 12:19 To_aru_Userさん:
ご連絡ありがとうございます。少なくとも2年半前には正しかったってことですね。他にも色々変わっているのかもしれません。この頃はZend_Http_Clientが採用されていたのが今はGuzzleに変更されてたりもするなど、今のGoutteは当時とは随分違うのかなと思っていますが、今の僕にこのあたりのニーズが無いのでキャッチアップできていません。このページにたどり着いた方には、そのつもりで読んでもらうしか無いかなと思います。

 | 
ページビュー
1841202