今更ながらPHPでスクレイピングをしてみる

More than 1 year has passed since last update.

以前は、PHP Simple HTML DOM Parserってのを使っていましたが、久しぶりに調べてみると、phpQueryというのがあるようなので、それを使ってみる。

オリジナルのものはここからダウンロードできるようです。
が、しばらくメンテされてないようです。ForkしてComposerに対応したものもあるようです(試してません)。

なお、あまりTableを対象としたサンプルが無かったのでテーブルのスクレイピングを中心に書きます。

シンプルなデモ

このようなHTML(test.html)があるとすると、

<!doctype html>
<html>
<head>
    <title>scraping test</title>
</head>
<body>
<h1>hoge</h1>
<!-- table1 -->
<div id="greeting">Hello</div>
<table>
    <tr>
        <td>name</td>
        <td>hoge</td>
    </tr>
    <tr>
        <td>email</td>
        <td>hoge@hoge.com</td>
    </tr>
</table>

<!-- table2 -->
<table>
    <tr>
        <td>address</td>
        <td>tokyo</td>
    </tr>
    <tr>
        <td>tel</td>
        <td>03-1234-5678</td>
    </tr>
    <tr>
        <td>image</td>
        <td><img src="hoge.png" atl="sample"></td>
    </tr>
    <tr>
        <td>detail</td>
        <td><a href="http://xxx/">more..</a></td>
    </tr>
</table>

<div class="btn">aaa</div>
<div class="btn">bbb</div>

</body>
</html>

例えば、タイトルを取得するのは、 下記のように取れるようである。

<?php

    //require
    require_once('phpQuery-onefile.php');

    //ページ取得
    $html = file_get_contents('./test.html');

    //DOM取得
    $doc = phpQuery::newDocument($html);

    //要素取得
    echo $doc["title"]->text();

なお、要素は、pq()メソッドを使って、

echo pq("title")->text();

と書くこともできるようです。
pq()は、jQueryの$()と等価であると共に、DOMをphpQueryで処理できるよう構造化する機能もあるようです。

基本的な使い方

$doc[""](もしくはpq(""))の""の中身はjQueryのセレクタが使えるので、基本、jQueryと同じように利用すればいいのですが、ちょっとしたクセもあるので、基本的な使い方のメモ。

必要な要素にidが振られていれば、操作に苦労はあまりないのですが、一意なidが振られていない場合は、要素の指定に少々工夫が必要です。

idで指定する

echo $doc["#greeting"]->text();

classで指定する

クラスの場合は複数の要素が存在するので、必要に応じて取得する要素を限定するようにします。

echo $doc[".btn:eq(1)"]->text();

静的に指定する

先述のHTMLには2行2列の2つのテーブルがあります。その2つ目のテーブルの1行目の2列目の要素(tokyo)を取得する場合は、

echo $doc["table:eq(1) tr:eq(0) td:eq(1)"]->text();

のように指定する。find()を利用して、

echo $doc["table:eq(1)"]->find("td:eq(1)")->text();

とも書けるようす。

foreachで回す

複数の値が返ってくる要素は、原則としてforeach()が利用できるようです。
ただ、tableの場合は、各td要素の値を個別に取得することができないようで、あまり使い勝手は良くありません。

foreach($doc["table:eq(0) tr"] as $row)
{
    //各要素取得
    $key   = pq($row)->find("td:eq(0)")->text();
    $value = pq($row)->find("td:eq(1)")->text();

    //表示
    echo $key . " " . $value . "<br>";
}

実務では、後述のcontains等を利用するこの方が多くなります。

利用サンプル

ここでは実開発で利用するサンプルを随時の記述していきます。

特定の行の値を抽出

実運用では、テーブルの場合、特定の要素を抜く需要が多いかとおもいます。
例えばemailという項目を値を取得する場合は、containsと+を利用して、となりの要素を指定したりします。

echo $doc["table"]->find("td:contains('email') + td")->text();

HTMLをそのまま取得

->text()ではなく、->html()を利用すれば、そのままのHTMLが取得できる。

$html = $doc["table:eq(1)"]->find("td:contains('image') + td")->html();

アトリビュートを取得

$src = $doc["table:eq(1)"]->find("td:contains('image') + td img")->attr("src");
$href = $doc["table:eq(1)"]->find("td:contains('detail') + td a")->attr("href");