[PHP]Goutte を使い倒すためのメモ

March 13, 2016

PHP の代表的な Web スクレイピング ライブラリ Goutte をだいぶ使ったので役に立ちそうなことのメモです。

Goutte

インストール

以前の Goutte は phar で提供されていましたが、最近になって Composer によるインストールになったみたい。ついでなので Composer のインストールから、コマンドのみ例示します。

Composer インストール

# cd /usr/local/bin
# curl -sS https://getcomposer.org/installer | php
# mv composer.phar composer

Goutte インストール

# cd path/to/goutte
# composer require fabpot/goutte

Goutte の使い方

大まかな流れは以下の通り。とても簡単です。

  1. Goutte の読み込み
  2. Goutte クライアントを作成
  3. ページをリクエスト
  4. 取得した Crawler オブジェクトのメソッドでスクレイピング
// Goutte の読み込み
require_once 'path/to/goutte/vendor/autoload.php';
use Goutte\Client;

// Goutte クライアントを作成
$client = new Client();

// ページをリクエスト
$crawler = $client->request('GET', 'http://example.com/');

// ページのタイトルをスクレイピング
$page_title = $crawler->filter('head > title')->text();

注意点

日本語.jp などのマルチバイト ドメイン名(IDN)を含む URL は、そのままでは GET(名前解決)に失敗します。事前に Punycode(ピュニコード)へのエンコードが必要です。ついでなので、よさげなエンコード / デコード ライブラリもご紹介。

クライアントと HTTP リクエスト

Goutte のクライアント モジュールは、SymfonyBrowserKit\Client をベースに、Guzzle HTTP Client ライブラリを組み合わせて構成されています。

Goutte クライアントの設定について、いくつか例を挙げます。

ユーザーエージェント(UA)の設定

デフォルトの「Symfony2 BrowserKit」から「My Crawler」に変更する例は以下です。

$client->setHeader('User-Agent', 'My Crawler');

Max Redirects の設定

リクエストしたページが301や302のリダイレクトを返してきたときに何回までそのリダイレクト先を辿るかを設定できます。デフォルトではリダイレクトを辿りません。

$client->setMaxRedirects(1);

尚、最後に辿り着いたURLは以下で取得できます。

$client->getRequest()->getUri();

また、リクエスト履歴として Symfony BrowserKit のヒストリー オブジェクトを参照できます。

スクレイピング

いよいよ本題。Goutte の HTML パーサー部分は Symfony の DomCrawler コンポーネントです。クライアント リクエストの戻り値として取得した Crawler オブジェクトを操作してページをスクレイピングできます。詳細は Symfony のドキュメントを参照。

以下にいくつか使用例を記載します。

DOM 要素のテキストを取得

CSS セレクタ形式の filter() メソッドと、XPath 形式の filterXPath() メソッドが使えます。尚、filterXPath() は、以下のように「descendant-or-self::」から始めた方が無難なようです。

// filterXPath() の例
$entry_title = $crawler->filterXPath('descendant-or-self::body/div[1]/h1');

// filter() の例
$entry_title = $crawler->filter('h1.entry-title')->text();
(参考)

DOM 要素の属性を取得

例えば、取得したページ内のすべてのアンカー(a)要素の href 属性は attr メソッドと each メソッドを使って以下のように取得できます。

$links = $crawler->filter('a')->each(function($node) {
    return $node->attr('href');
});

$links は href 属性値の配列になります。

特定の DOM 要素を除外

reduce() というメソッドがあります。無名関数の戻り値として FALSE を返すと、そのノードが除外されます。

// 偶数番目の li 要素のみを取得
$li_even = $crawler->filter('ul li')->reduce(function($node, $i) {
    return ($i % 2) == 0;
});

Goutte を HTML パーサーとして使う

Crawler オブジェクトを単体で使って任意の HTML 文字列をパースできます。

use Symfony\Component\DomCrawler\Crawler;

$crawler = new Crawler();
$crawler->addHtmlContent('<a href="/link/to/page/"><img alt="example" src="image.example.com/image.jpg" /></a>');

$img_src = $crawler->filter('img')->attr('src'); // image.example.com/image.jpg

Goutte で POST や HEAD リクエスト

せっかくGoutteを導入したなら、POST リクエストは file_get_contents() より Goutte の方が簡単です。ステータス コードだけ取得したい(200 なのか 404 なのかだけ知りたい)場合は、ヘッダ情報のみをリクエストする HEAD を使うと帯域に優しくてオススメ。

// POST
$client->request('POST', 'http://example.com/form/', ['name' => 'Hello', 'message' => 'World']);

// HEAD
$client->request('HEAD', 'http://example.com/health-check/');

$status_code = $client->getInternalResponse()->getStatus();

リクエスト部分は Guzzle です。PUT や DELETE なども可能。

HTTP レスポンス

Goutte クライアントはデフォルトで Crawler オブジェクトを返しますが、JSON が返される API を GET する場合など、必要に応じて Symfony BrowserKit のレスポンス オブジェクトを参照することもできます。いくつか使用例を記載します。

$client->request('GET', 'api.example.com/data/?ID=100');

// レスポンス ボディの取得
$json = json_decode($client->getInternalResponse()->getContent());

// ステータス コードの取得
$status = $client->getInternalResponse()->getStatus();

// レスポンス ヘッダの取得
$response_headers = $client->getInternalResponse()->getHeaders();

Goutte は XML もパースしてスクレイピングできる

以前の記事で「XMLがパースできない」と書きましたが、できました。

フィルターするときに名前空間の指定が必要なようです。デフォルトの名前空間は「default」なので、例えばサイトマップのロケーション(url)一覧を取得するなら以下になります。

$sitemap = $client->request('GET', 'http://example.com/sitemap.xml');

$urls = $sitemap->filter('default|loc')->each(function($node) {
    return $node->text();
});

その他、詳しくは先ほどリンクした Symfony の公式ドキュメントをご覧ください。

The default namespace is removed when loading the content if it’s the onlynamespace in the document. It’s done to simplify the xpath queries.

などの記載に罠の香りを感じます。

リクエストやスクレイピングのエラー処理

ページをリクエストする($client->request)ときやスクレイピングする(filter, filterXPath)ときなどにエラーが発生した場合、各コンポーネントから例外(exception)がスローされます。try ~ catch で適宜、処理する必要があるでしょう。

$client = new Client();
try {
	$crawler = $client->request('GET', 'http://example.com/');
} catch (Exception $ex) {
	error_log(__METHOD__.' Exception was encountered: '.get_class($ex).' '.$ex->getMessage());
}

...

try {
	$links = $crawler->filter('a')->each(function($node) {
	    return $node->attr('href');
	});
} catch (Exception $ex) {
	error_log(__METHOD__.' Exception was encountered: '.get_class($ex).' '.$ex->getMessage());
}

All You Need Is Goutte

これで Goutte はあなたのものだ。

One thought on “[PHP]Goutte を使い倒すためのメモ

  1. Pingback: Goutte(PHP)でスクレイピングするときのメモ | Design Hack and Slash

コメントを残す

メールアドレスが公開されることはありません。