Help us understand the problem. What is going on with this article?

Goutte(PHP)でスクレイピングしてみる

More than 3 years have passed since last update.

少し前、phpQueryというライブラリを利用してみたが、使い勝手は良かったがComposer対応ができてないので、Composer対応バッチリのGoutte(ゲットと発音するらしい)てのを利用してみました。特にテーブルの処理に重きを置いています。

結論

住めば都なのしょうが、Goutteは、ちょっと「クセ」がありました。

クセというか、他のライブラリとの一番の違いは「無い要素を抽出しようとするとエラーになる」ということです。ですので、要素があるかないかを意識してコードを書く必要があります(他のライブラリは基本、無い場合は無視という振る舞いのため、困惑します)。

他のサイト等でも、エラー処理が必要だ!というような記述は見かけましたが、こういうものとは思いませんでしたね。

インストール

利用したいディレクトリで、

composer require fabpot/goutte

とします。vendor/autoload.phpをrequire_onceで読み込みます。

使用例(クセの例)

下記のようなHTMLがあるとします。

サンプルHTML

2つのテーブルとYahooへのリンクがあります。ポイントは、テーブル<tr>の中身です。
1個目のテーブルの1行目はいわゆるヘッダー行でth要素のみからなり、td要素が含まれていません。これを覚えておいて下さい。

<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<title>test page</title>
</head>
<body>
<table border=1 class="table hoge" id="table1">
    <tr>
        <th>no</th>
        <th>name</th>
        <th>emai</th>
    <tr>
    <tr>
        <th>1</th>
        <td>aaa</td>
        <td>aaa@test.local</td>
    </tr>
    <tr>
        <th>2</th>
        <td>bbb</td>
        <td>bbb@test.local</td>
    </tr>
</table>
<br>
<br>
<table border=1 class="table foo" id="table2">
    <tr>
        <td>ccc</td>
        <td>ccc info</td>
    </tr>
    <tr>
        <td>ddd</td>
        <td>ddd info</td>
    </tr>
</table>
<br>
<br>
<a href="http://www.yahoo.co.jp">Yahooに移動</a>
<br>
<img src="https://avatars.githubusercontent.com/u/3616214?v=2">
</body>

ニーズとアプローチ

ここでのスクレイピングのニーズとしては、1個目のテーブルの各要素を取得したいものとします。
取得に際しての疑問としては、

  • 1個目のテーブルをどう取得するか?
  • td各要素にどうアプローチするか?

の2つが大きなポイントかと思います(クセは後者の際に総合しました)。

全テーブルのtr要素を取得

まず、テーブルを意識せず、tr要素を抜きたい!と思うと、下記のようになります。

<?php

    //ライブラリロード
    require_once './vendor/autoload.php';

    //use
    use Goutte\Client;

    //インスタンス生成
    $client = new Client();

    //取得とDOM構築
    $crawler = $client->request('GET','http://localhost/test/test.html');

    //要素の取得
    $tr = $crawler->filter('table tr')->each(function($element){
        echo $element->text()."\n";
    });

複数ある要素を取得したい場合は、->each()とすることにより、要素分ループできます。

特定のテーブルの要素のみを取得する

上記の例では、違う構造のテーブルの情報が取得されてしますので、実務上は実用性がありません。
実務的には、テーブルを特定して取得する必要があります。

テーブルを特定する方法はいくつかあります。
大きくは、

  • ID
  • CLASS
  • INDEX(タグの登場順位)

の3つになります。

ID

IDが振られていれば、まあ、わけはありません。
指定方法もCSS等でお馴染みなので、今更どうこうというものではありません。

    //tr要素の取得
    $tr = $crawler->filter('table#table1' tr)->each(function($element){
        echo $element->text()."\n";
    });

CLASS

クラスの場合は、重複が前提となります。複数のクラス名で特定できる場合は、.で繋げることで特定できる場合もあります。

    //tr要素の取得
    $tr = $crawler->filter('table.table.hoge tr')->each(function($element){
        echo $element->text()."\n";
    });

INDEX(タグの登場順位)

タグの登場順位を目視で確認し、番号で指定することもできます。

    //tr要素の取得
    $tr = $crawler->filter('table')->eq(0)->filter('tr')->each(function($element){
        echo $element->text()."\n";
    });

td要素の取得(クセ)

td要素を取得したい場合、普通に考えれば、tableのtdをeachで回せばいいのかな?と思い、下記のように書いてみました。

    //tr要素の取得
    $tr = $crawler->filter('table')->eq(0)->filter('tr')->each(function($element){

        echo $element->filter('td')->eq(1)->text()."\n";

    });

が、この場合エラーが出ます。

Fatal error: Uncaught exception 'InvalidArgumentException' with message 'The current node list is empty.' in /Applications/MAMP/htdocs/test/vendor/symfony/dom-crawler/Crawler.php:550

要は要素がないぞ!と怒られているようです。しばらくはまってたのですが、どうやら下記のように書けばOKのようです。

    //tr要素の取得
    $tr = $crawler->filter('table')->eq(0)->filter('tr')->each(function($element){

        //td要素があるときのみ取得処理を行う
        if(count($element->filter('td'))){
            echo $element->filter('td')->eq(1)->text()."\n";
        }

    });

そもそも、$crawler直下では、

$tr = $crawler->filter('table')->eq(5)->filter('tr')->each(function($element){});

というように、無い要素(テーブル)を照会しても何のエラーも出ません。。。慣れの問題でしょうが、一貫性が無いようにも感じられますね。

細かくドキュメントや仕様を呼んでいませんが、今のところ、2階層以下のfilter処理では、要素の有無を気にしないといけないのかな?というところです(どなたか明確なルールを知ってる人おしえて下さい)。

その他の要素

アトリビュート

アトリビュートは下記のように取得できます。

aのhref

    $tr = $crawler->filter('a')->each(function($element){

        echo $element->attr('href');

    });

imgのsrc

    $tr = $crawler->filter('img')->each(function($element){

        echo $element->attr('src');

    });

最後に

始めての利用で、クセのある書き方を要求されたので少々戸惑いましたが、全体としてはバランスの良いライブラリかなとも思います。

zaburo
こんにちは。自分用のメモをだらだら公開しています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした