アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

Webスクレイピングが捗るGoutteを使ってみる



シャワー後の水切りでヘドバンしてたら頸椎を痛めてしまいました。あれは絶対やめた方がいいです。と周囲に広めているたきゃはしです。急に本題ですが今回はPHPで簡単にできるWebスクレイピングをご紹介します。

◯ Webスクレイピングとは



Webサイトからデータを抽出するソフトウェア技術のことです。
RSSやWebAPIが公開されていないサイトからでもデータ抽出が出来るようなイメージです。

早速クローラー Goutte(グットゥ) を使って紹介していきたいと思います。
Goutte は Symfony や Twig、Pimple等の開発者として知られるFabienが手がける人気ライブラリです。

◯ インストール




php composer.phar require fabpot/goutte:~2.0

インストールが完了したら vendor/fabpot/goutte/composer.json を覗いてみます...


  ...
  "require": {
    "php": ">=5.4.0",
    "symfony/browser-kit": "~2.1",
    "symfony/css-selector": "~2.1",
    "symfony/dom-crawler": "~2.1",
    "guzzlehttp/guzzle": "4.*"
  },
  ...

BrowserKit に Guzzle とくればどんな機能があるかなんとなく想像がつきますね。
話が逸れますが、使用するライブラリの依存コンポーネントを確認しておくことは大切かなと思います。

◯ 使ってみる





<?php
// first.php
require_once './vendor/autoload.php';

$client = new Goutte\Client();
$crawler = $client->request('GET', 'http://blog.asial.co.jp/');

// 抽出
$targetSelector = 'h2.lh1_2em'; // アシアルブログの見出しのセレクター
$crawler->filter($target)->each(function ($node) {
    echo $node->text() . "\n";
});



~/Sites/prac/goutte  php first.php
  外部コンテンツをiframeサイズで拡大縮小させたり、固定幅コンテンツをウィンドウサイズでピッタリ表示させる方法
  「Monaca for Hybridcast」CEATEC JAPAN 2014(2014/10/7-11開催)にて展示
  お絵描きアプリと画像の保存処理の実装
  PC/スマホでは無いWEBアプリ開発の話 -ハイブリッドキャスト編-
  IllustratorでSVGファイルを保存してみました
  iPhone6 PlusのPSDモックアップとAppleのフォント
  Canvas Fingerprintingというトラッキング技術
  HTML5プロフェッショナル認定試験のセミナー資料を公開します
  弊社田中のコラム「エンタープライズHTML5とバックエンド─エンタープライズ×モバイルアプリ開発の最新動向」が公開されました
  HTML5+CSS3+JSでネイティブGUIアプリが作れる、node-webkitを触ってみる

試しにアシアルブログの記事タイトルを抽出してみました。実質10行も書いてないです。
コードにしれっと出てきた $client と $crawler について確認してみたいと思います。

・$client Goutte\Client



Array
(
    [0] => setClient
    [1] => getClient
    [2] => setHeader
    [3] => removeHeader
    [4] => setAuth
    [5] => resetAuth
    [6] => __construct
    [7] => followRedirects
    [8] => setMaxRedirects
    [9] => insulate
    [10] => setServerParameters
    [11] => setServerParameter
    [12] => getServerParameter
    [13] => getHistory
    [14] => getCookieJar
    [15] => getCrawler
    [16] => getInternalResponse
    [17] => getResponse
    [18] => getInternalRequest
    [19] => getRequest
    [20] => click
    [21] => submit
    [22] => request
    [23] => back
    [24] => forward
    [25] => reload
    [26] => followRedirect
    [27] => restart
)

定義されているメソッドのほとんどがsetter&getterですね。
このオブジェクトの役割はブラウザもしくはページそのものみたいな感覚でいいかなと思います。
click, submit, request, back, forward, reload, restart あたり覚えておけば良さそうです。

・$crawler Symfony\Component\DomCrawler\Crawler



Array
(
    [0] => __construct
    [1] => clear
    [2] => add
    [3] => addContent
    [4] => addHtmlContent
    [5] => addXmlContent
    [6] => addDocument
    [7] => addNodeList
    [8] => addNodes
    [9] => addNode
    [10] => eq
    [11] => each
    [12] => reduce
    [13] => first
    [14] => last
    [15] => siblings
    [16] => nextAll
    [17] => previousAll
    [18] => parents
    [19] => children
    [20] => attr
    [21] => text
    [22] => html
    [23] => extract
    [24] => filterXPath
    [25] => filter
    [26] => selectLink
    [27] => selectButton
    [28] => link
    [29] => links
    [30] => form
    [31] => setDefaultNamespacePrefix
    [32] => registerNamespace
    [33] => xpathLiteral
    [34] => getNode
    [35] => attach
    [36] => detach
    [37] => contains
    [38] => addAll
    [39] => removeAll
    [40] => removeAllExcept
    [41] => getInfo
    [42] => setInfo
    [43] => getHash
    [44] => count
    [45] => rewind
    [46] => valid
    [47] => key
    [48] => current
    [49] => next
    [50] => unserialize
    [51] => serialize
    [52] => offsetExists
    [53] => offsetSet
    [54] => offsetUnset
    [55] => offsetGet
)

見た感じ配列のユーティリティクラスを継承してるようですね。
クローラーオブジェクトの役割はデータ抽出の作業者でよさそうです。
eq, attr, text, html, extract, selectLink, selectButton, link, links, form あたり覚えておけば良さそうです。
配列操作だと each, first, last, filter, contains, count あたりはよく使いそうですね。

さて、クライアントやクローラの内容をちょこっと予習できたところで、他の操作もしてみたいと思います。

・テキストリンクをクリックする




<?php
...
// リンクのテキストをクリックする
$targetLinkText = 'バックエンド(プログラミング)';
$link = $crawler->selectLink($targetLinkText)->link();
$crawler = $client->click($link);

ページの遷移に関しては同期処理となっていてWaitを実装する必要はなさそうです。

・ボタンをクリックする




<?php
...
// ボタンのテキストをクリックする
$targetButtonText = '検索';
$button = $crawler->selectButton($targetButtonText)->form();
$crawler = $client->click($button);

なんとブログにbuttonタグがありませんでした。。。なかったのですが
inputタグでも出来ることが分かって良かったです。(type=imageのaltテキストを対象にしました。)

・フォームを送信する




<?php
...
// 指定テキストで検索する
$targetButtonText = '検索';
$form = $crawler->selectButton($targetButtonText)->form();
$searchParameters = ['words' => 'Monaca'];
$crawler = $client->submit($form, $searchParameters);

formタグのactionやmethodを使用して処理してくれます。

また$clientはリクエスト&レスポンスを持っていました。以下のメソッドが定義されています。

Symfony\Component\BrowserKit\Request



Array
(
    [0] => __construct
    [1] => getUri
    [2] => getMethod
    [3] => getParameters
    [4] => getFiles
    [5] => getCookies
    [6] => getServer
    [7] => getContent
)

Symfony\Component\BrowserKit\Response



Array
(
    [0] => __construct
    [1] => __toString
    [2] => getContent
    [3] => getStatus
    [4] => getHeaders
    [5] => getHeader
)

「なるほどぉ〜」くらいに思ってもらえればと思います。

また記事作成中に知った機能ですが、Chrome Developer Tools > Copy CSS Path(画像参照) がセレクター取得に便利でした。


◯ まとめ



Goutteでスクレイピングしてみて良かったなぁという点は...



  • めちゃくちゃ簡単にスクレイピングできること。

  • 手作業に比べて圧倒的に高速かつ正確なこと。

  • 人間が詳細な作業内容を覚える必要がないこと。

  • 作業という単位でコンポーネント化しやすいので開発がどんどん楽になりそうなこと。

  • ちょっとずれるけど、PHPUnitと連携すれば画面テストにも利用できること。


逆に課題かなぁと感じた点は...



  • プログラムがWebサイトの稼働状況やDOM構造に依存しているため安定稼働が難しいこと。

  • 自動化する場合は対象のDOM構造が変更されていないか監視する機能が必要なこと。

  • 変更があれば効率よく開発者へ通知するためのロギング機能や通知処理が必要なこと。

  • 変更があれば抽出ミスが発生するため、エラー処理やリカバリが必要なこと。


要約すると、すごい便利なんだけど作りこまないと運用大変そうだなぁという感じでした。

今更言うことではないですが、規約に反する行為および抽出データの取り扱いにはくれぐれもご注意を。
それでは皆様、健全で生産的なスクレイピングを楽しみましょう!

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)