アシアルブログ

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

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構造が変更されていないか監視する機能が必要なこと。

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

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


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

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