ことさら−古都プログラマーの更級日記

京都でお寺を回りながら御朱印集めをしていたり、LoLをしたり試合を見に行ったりしているエンジニアのブログです。技術的なはなしとか日常的なはなし、カメラやLoLや競馬の話も書きます。右メニューに検索やらカテゴリーやらがあるので、見たい記事だけ見てね!

PHP で GuzzleHttp と DOMDocument/DOMXPath を使ってページのスクレイピングをする

はじめに

この記事は PHP Advent Calendar 2017 - Qiita の22日目の記事です。

昨日の記事は Khigashiguchi さんの PHPでTDD開発するまでに通った道のり - Qiita でした。

PHP で Web ページのスクレイピングをする

ページのスクレイピングといえば、RubyでNokogiriを使ってやったりというイメージがあるのですが、いろんな言語でスクレイピングできるようになっておいたほうがいいかなtお思い、今回はPHPでサクッと出来ないか試してみました。

利用するのはGuzzleHttpとDOMDocument

DOMDocument

DOMDocumentはPHPの標準ライブラリてきなやつです。

PHP: DOMDocument - Manual

HTML(XML)を解析してくれます。標準ライブラリなので特別にインストールをする必要はありません。

DOMXpath

XPathを書くとDOMDocumentの中からDOMを探してきてくれます。

PHP: DOMXPath - Manual

これもPHP標準で入っているのでインストール等の必要はありません。

GuzzleHttp

GuzzleHttpは、PHPだと一番メジャーかなと思われるHTTPリクエストを行うためのライブラリです。

github.com

GuzzleHttpのインストール

composerを使ってインストールします。composerの使い方に関しては省略します。

$ cat composer.json
{
    "name": "yoshikyoto/php-jra",
    "authors": [],
    "autoload": {
        "psr-4": {
            "Jra\\" : "src"
        }
    },
    "require": {
        "guzzlehttp/guzzle": "6.0"
    }
}

$ composer install

スクレイピングしてみる

今回は、JRAのトップページの「今週の開催競馬場・注目レース」に表示されているレースのURL一覧を取得してくることにします。

f:id:yoshiki_utakata:20171222115447p:plain

GuzzleHttpを使ってHTTPリクエストを送る

GuzzleHttpを使って、簡単なHTTPリクエストを送ってみます。

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

$client = new \GuzzleHttp\Client([
    'base_uri' => 'http://www.jra.go.jp',
]);

$response = $client->get('/');
var_dump($response->getBody()->getContents());

とても分かりやすいです。JRAのページにリクエストを送ってみて、結果のHTMLをvar_dumpするコードです。

Guzzleで受け取った結果をDOMDocumentに渡して解析する

ではGuzzleで受け取ったHTMLをDOMDocumentに渡して目的の情報を取得してみようかと思います。

$client = new \GuzzleHttp\Client([
    'base_uri' => 'http://www.jra.go.jp',
]);
$response = $client->get('/');

$document = new \DOMDocument();
@$document->loadHTML($response->getBody()->getContents());

GuzzleHttpで取得してきたHTML列を、そのままDOMDocumentのloadHTMLにかませます。ここで注意が必要なのが、 loadHTML の行にある @ です。 @PHPの文法で、「その行で発生するエラーメッセージを抑制する」というものです。Webをスクレイピングしていると、開始タグに対して終了タグが無いHTMLなど、不正なHTMLが大量にあります。Webブラウザはこれをいい感じに解釈して表示してくれるので問題ありませんが、XMLとしては不正なため、大量のWarningが出てしまいます。そこで、@を付けることでWarningが出るのを抑制します。Warningは出ますがDOMの解析は大抵正しくできています。

DOMXPath を使ってDOMを取得する

DOMXPath を使って、注目レースのaタグを取得してきます。

$document = new \DOMDocument();
@$document->loadHTML($response->getBody()->getContents());
$xpath = new \DOMXPath($document);
$nodes = $xpath->query('//div[@class="race"]//a');
var_dump($nodes);

実行結果

$ php test.php
object(DOMNodeList)#31 (1) {
  ["length"]=>
  int(3)
}

XPathの書き方は PHPネイティブのDOMによるスクレイピング入門 - Qiita のあたりが参考になります。

//div[@class="race"]//a はページにあるdivのうち、class="race" なものを取ってきて、その中にあるaタグを取得してきます。

DOMの中の属性などを取得する

最後にaタグの中身とhref属性を取ってこようかと思います。

$nodes = $xpath->query('//div[@class="race"]//a');

foreach($nodes as $node) {
    echo $node->nodeValue . "\t" . $node->getAttribute('href') . PHP_EOL;
}

nodeValue はそのDOMで囲まれた中身の値を取得できます。

getAttribute はそのDOMに指定された属性の値を取得できます。

実行結果

$ php test.php
中山大障害 /keiba/thisweek/2017/1223_1/
有馬記念    /keiba/thisweek/2017/1224_1/
阪神カップ /keiba/thisweek/2017/1223_2/

まとめ

以上の知識があればHTMlのスクレイピングが可能となります。

今回はリクエストの部分を使いやすくするために、GuzzleHttpというライブラリを利用しましたが、file_put_contents 等を使えばライブラリを導入することなくスクレイピングすることもできます。

ぜひ皆さんもPHPスクレイピングしてみましょう。