CakePHP 4 で HTML の解析(パース)を行う方法

はじめに

Web 開発を行っていると、クローラのスクレイピングや既存データの再利用などで HTML 解析を行うことがあります。

今日は CakePHP 4 で URL や ファイル から HTML を解析する方法をご紹介します。

今回使用したのは CakePHP 4.0.7 です。

目次
  1. 下準備
  2. HTML 解析
  3. おわりに

1. 下準備

今回は解析対象として下記 HTML ファイルを作成しました。
サーバ内にあるファイルの読取りを想定し /data フォルダに置いています。

/data/target.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample Page</title>
</head>
<body>
  <table>
    <tr class="item fruit">
      <th>オレンジ</th>
      <td class="price" data-value="200">200円</td>
    </tr>
    <tr class="item fruit">
      <th>ぶどう</th>
      <td class="price" data-value="400">400円</td>
    </tr>
    <tr class="item drink">
      <th>牛乳</th>
      <td class="price" data-value="150">150円</td>
    </tr>
  </table>
</body>
</html>

インターネット上に公開されているデータを解析する場合でも、相手サーバ負荷軽減のため、開発時にはその HTML データを保存して解析した方がいいと考えています。

例えば、解析対象のパスや URL を app_local.php に追加することで、開発時と運用時とで簡単に切り替えることができます。

/config/app_local.php
...
return [
    'sampleUrl' => ROOT . DS . 'data' . DS . 'target.html',
    ...

2. HTML 解析

HTML の解析は以下のように実装することができます。
先に紹介した HTML ファイルから、フルーツの名前と価格を取得しています。

/src/Controller/SamplesController.php
<?php
declare(strict_types=1);

namespace App\Controller;

// ▼ これらを追加
use Cake\Core\Configure;
use Cake\Utility\Xml;
use DOMXPath;

class SamplesController extends AppController
{
    public function index()
    {
        // HTML 文字列読込
        $url = Configure::read('sampleUrl');
        $htmlString = file_get_contents($url);

        // DOMXPath オブジェクトを生成
        $html = Xml::loadHtml($htmlString, ['return' => 'domdocument']);
        $xpath = new DOMXPath($html);

        // 商品情報を取得
        $fruitNodes = $xpath->query('//*[contains(@class, "fruit")]');
        $fruits = [];
        foreach ($fruitNodes as $fruitNode) {
            $fruits[] = [
                'name' => $xpath->query('./th', $fruitNode)
                    ->item(0)
                    ->nodeValue,
                'price' => $xpath->query('./td[@class="price"]', $fruitNode)
                    ->item(0)
                    ->getAttribute('data-value')
            ];
        }

        debug($fruits);
        exit;
    }
}
実行結果
[
  (int) 0 => [
    'name' => 'オレンジ',
    'price' => '200'
  ],
  (int) 1 => [
    'name' => 'ぶどう',
    'price' => '400'
  ]
]

app_local.php の sampleUrl を書き換えることで、解析対象を簡単に切り替えることができます。sampleUrl はサーバ内のファイルパスでも、外部 URL でも可能です。

CakePHP 特有の機能というのはほとんどなく、ほぼ PHP 標準の DOMXPath を使って行っています。Xml::loadHtml では DOMDocument を取得しています。

下記公式ドキュメントを参考にするときには DOMElement は DOMNode を継承していることを頭に置いておくと理解しやすいと思います。

また DOMXPath の query() では XPath という構文をつかいます。検索サイトを使えば様々な参考文献が見つかると思います。

僕は下記の記事を参考にしました。とても分かりやすいと思います。

3. おわりに

PHP 標準の機能が十分ということなのか、CakePHP でも HTML 解析には DOMDocument を使っています。

サンプルにある Xml::loadHtml() の箇所も、今のところ使うメリットが分からず、PHP 標準の DOMDocument クラスを使って書いても良いのかなとも思っています。