技術資料

PHP 8.3でHTMLのtableを解析する:DOMDocumentとDOMXPathで一覧データを抽出

作成日:2026.09.05

PHP

PHP 8.3のDOMDocumentとDOMXPathを使い、HTMLファイルに記載された一覧データから、特定の情報を抽出する方法を解説します。DOMElementやDOMNodeの扱い、XPathによるtableの検索、不正なHTMLや日本語文字コードへの注意点に加え、外部URLからHTMLを取得する場合に確認したい利用条件やサーバー負荷についても説明します。

HTMLファイルの中にある一覧表から、必要なデータだけを取り出したい時の方法についてまとめました。

今回は、PHP 8.3のDOMDocumentとDOMXPathを使って、全国自然史博物館一覧を記載したサンプル用HTMLファイルを読み込みます。表の行から、博物館名と都道府県・市区町村の住所を抽出して、配列へ変換してみます。

HTMLを文字列として正規表現で処理するのではなく、HTMLの要素をDOMツリーとして扱います。DOMElement、DOMNodeの役割や、表の行が見つからない場合の処理も確認します。

前提

今回の動作確認環境は以下の通りです。

  • OS: Windows 11
  • PHP: 8.3.15 CLI
  • ターミナル: PowerShell
  • 使用する拡張: DOM、libxml、mbstring
  • 入力: ローカルに保存したHTMLファイル

PHPのDOM拡張は、HTMLやXMLをノードの階層として扱うための機能です。

なお、PHP 8.3のDOMDocument::loadHTMLFile()は、現在のブラウザーと同じHTML5の解析規則を使うわけではありません。PHP 8.4以降にはHTML5に対応するDom\HTMLDocumentが追加されていますが、この記事ではPHP 8.3のDOMDocumentを対象にします。

今回使うHTMLファイルを用意する

まず、PHPスクリプトと同じディレクトリにsample-museums.htmlを作成します。表には、博物館名と都道府県・市区町村を含む所在地を記載します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>自然史博物館一覧</title>
</head>
<body>
    <h1>全国の自然史博物館</h1>

    <table id="museum-list">
        <thead>
            <tr>
                <th>博物館名</th>
                <th>所在地</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>北海道大学総合博物館</td>
                <td>北海道札幌市</td>
            </tr>
            <tr>
                <td>国立科学博物館</td>
                <td>東京都台東区</td>
            </tr>
            <tr>
                <td>福井県立恐竜博物館</td>
                <td>福井県勝山市</td>
            </tr>
            <tr>
                <td>大阪市立自然史博物館</td>
                <td>大阪府大阪市</td>
            </tr>
            <tr>
                <td>北九州市立いのちのたび博物館</td>
                <td>福岡県北九州市</td>
            </tr>
        </tbody>
    </table>
</body>
</html>

実際のWebページでは、表の列が増えたり、行の中にリンクやアイコンが入ったりすることがあります。今回は処理を分かりやすくするため、1行目のセルを博物館名、2行目のセルを所在地として扱います。

HTMLファイルをDOMDocumentへ読み込む

HTMLファイルを読み込むには、DOMDocumentのloadHTMLFile()を使います。戻り値は、読み込みに成功するとtrue、失敗するとfalseです。

不正なHTMLを読み込んだ場合でも、補正しながら読み込みが成功することがあります。そのときに出力されるlibxmlの警告を確認できるよう、内部エラーを一時的に有効にします。

$inputFile = __DIR__ . '/sample-museums.html';

if (!is_file($inputFile) || !is_readable($inputFile)) {
    throw new RuntimeException('HTMLファイルを読み込めません。');
}

$previousUseErrors = libxml_use_internal_errors(true);
libxml_clear_errors();

try {
    $document = new DOMDocument();
    $loaded = $document->loadHTMLFile($inputFile, LIBXML_NONET);
    $parseErrors = libxml_get_errors();
} finally {
    libxml_clear_errors();
    libxml_use_internal_errors($previousUseErrors);
}

if (!$loaded) {
    throw new RuntimeException('HTMLの解析に失敗しました。');
}

foreach ($parseErrors as $parseError) {
    fwrite(STDERR, trim($parseError->message) . PHP_EOL);
}

LIBXML_NONETは、解析時にネットワークへアクセスしないためのオプションです。今回の入力はローカルファイルですが、HTML内に外部リソースへの参照があっても、解析処理からネットワークへ接続しないように指定しています。

libxml_use_internal_errors(true)を使うと、通常のエラー出力へ直接表示せず、libxml_get_errors()でエラー情報を取得できます。処理が終わったらlibxml_clear_errors()でバッファを消し、元の設定へ戻します。

今回のようなサンプルHTMLでは、$parseErrorsは空になります。不正なHTMLで警告が発生しても、警告があることだけを理由に処理を中断するかどうかは、アプリケーションの目的に合わせて決めます。少なくとも、警告の有無とloadHTMLFile()の戻り値は別に扱います。

DOMDocument・DOMElement・DOMNodeの役割

DOMでは、HTML全体や要素をノードのつながりとして扱います。

  • DOMDocument: HTML文書全体を表すオブジェクト
  • DOMElement: tableやtr、tdなどの要素を表すオブジェクト
  • DOMNode: 要素やテキストノードなど、DOMツリー上のノードを表す基底的なオブジェクト
  • DOMXPath: XPath式を使って、DOMツリーから対象のノードを検索するオブジェクト

要素の表示文字列を取得するときは、DOMNodeのtextContentを使えます。要素の属性を取得するときは、DOMElementのgetAttribute()を使います。

例えば、先ほど読み込んだ文書から、一覧表のid属性を取得する処理は次のようになります。

$tables = $document->getElementsByTagName('table');
$table = $tables->item(0);

if ($table instanceof DOMElement) {
    echo $table->getAttribute('id'), PHP_EOL;
    // museum-list
}

getElementsByTagName()でもタグ名による検索はできますが、複数の表があるページで条件を細かく指定したい場合は、次のDOMXPathを使う方が意図を表しやすくなります。

DOMXPathでtableの行を検索する

DOMXPathを作成し、query()へXPath式を渡します。今回は、idがmuseum-listである表のうち、tdを含む行だけを検索します。

$xpath = new DOMXPath($document);
$rows = $xpath->query('//table[@id="museum-list"]//tr[td]');

if ($rows === false) {
    throw new RuntimeException('XPathの評価に失敗しました。');
}

echo $rows->length, PHP_EOL;
// 5

//table[@id="museum-list"]は、id属性が一致するtableを表します。続く//tr[td]は、その表の中から、tdを含むtrを表します。

見出し行はthを使っているため、このXPathの結果には含まれません。また、HTMLの解析時にtbodyが補われることがあっても、//trで検索するため、今回の処理では影響を受けにくくなります。

博物館名と住所を配列へ変換する

検索した各行から、子要素であるtdを取得します。セル内に改行や余分な空白が含まれる場合に備えて、テキストを1つの空白へ正規化する関数も用意します。

function normalizeText(DOMNode $node): string
{
    $text = preg_replace('/\s+/u', ' ', $node->textContent);

    return trim($text ?? '');
}

$museums = [];

foreach ($rows as $row) {
    if (!$row instanceof DOMElement) {
        continue;
    }

    $cells = $xpath->query('./td', $row);

    if ($cells === false || $cells->length < 2) {
        continue;
    }

    $nameNode = $cells->item(0);
    $addressNode = $cells->item(1);

    if (!$nameNode instanceof DOMNode || !$addressNode instanceof DOMNode) {
        continue;
    }

    $name = normalizeText($nameNode);
    $address = normalizeText($addressNode);

    if ($name === '' || $address === '') {
        continue;
    }

    $museums[] = [
        'name' => $name,
        'address' => $address,
    ];
}

query()の第2引数へ$rowを渡すと、文書全体ではなく、その行を基準に./tdを検索できます。./は、現在の行の直下を基準にする指定です。

今回のコードでは、セルが2つ未満の行や、博物館名・住所が空の行は結果へ追加しません。入力データによっては空値を保持したい場合もあるため、スキップするかどうかは用途に合わせて変更してください。

抽出結果を表示する

抽出した配列を表示して、期待した件数と値になっているか確認します。

echo '取得件数: ' . count($museums) . PHP_EOL;

foreach ($museums as $museum) {
    echo $museum['name'] . ' / ' . $museum['address'] . PHP_EOL;
}

今回のサンプルでは、次のような結果になります。

取得件数: 5
北海道大学総合博物館 / 北海道札幌市
国立科学博物館 / 東京都台東区
福井県立恐竜博物館 / 福井県勝山市
大阪市立自然史博物館 / 大阪府大阪市
北九州市立いのちのたび博物館 / 福岡県北九州市

この時点では、HTMLの見た目を再現しているのではなく、DOMツリー上のセルからテキストを取り出しています。取得した配列をJSONに変換したり、データベースへ保存したりする処理は、抽出処理の後ろへ追加できます。

PHPスクリプトを実行する

ここまでの処理をparse-museums.phpへまとめ、sample-museums.htmlと同じディレクトリへ保存します。PowerShellでPHPのバージョンと拡張を確認します。

php -v
php -m | Select-String "dom|libxml|mbstring"

続いて、PHPスクリプトを実行します。

php parse-museums.php

次の内容を確認します。

  • HTMLファイルが読み込まれ、5件の行が検索できる
  • 博物館名と都道府県・市区町村の住所が配列へ入っている
  • 日本語の文字列が文字化けしていない
  • セルが不足した行を誤って結果へ追加していない
  • 対象の表がない場合に、0件として扱うかエラーにするかが決まっている

不正なHTMLと日本語文字コードの注意点

DOMDocument::loadHTMLFile()は、XMLのように完全に整ったHTMLだけを受け付けるわけではありません。不正なマークアップを補正して読み込むことがありますが、補正後のDOM構造が、作成者の想定やブラウザーの表示結果と一致するとは限りません。

そのため、解析対象のHTMLに不備がある場合は、次の内容を分けて確認します。

  • ファイルが存在しない、または読み込めない
  • HTMLの解析自体に失敗した
  • 解析はできたが、対象のtableが存在しない
  • 表は存在するが、対象の行やセルが存在しない
  • 値は取得できたが、文字コードが合わず文字化けしている

日本語を含むHTMLでは、HTMLファイルの保存形式と、HTML内のmeta charset、実際の文字コードが一致しているかを確認します。DOM拡張はUTF-8を前提に動作するため、別の文字コードのHTMLを扱う場合は、読み込み前の変換方法を検討してください。

また、PHP 8.3のDOMDocumentはHTML5準拠のサニタイズ機能ではありません。取得したHTMLをそのまま安全なHTMLとして再出力したり、利用者へ表示したりする用途では、別途サニタイズ処理とセキュリティ検証が必要です。

外部URLからHTMLを取得する場合の注意点

今回はローカルファイルを読み込みました。外部URLからHTMLを取得する処理まで追加すると、HTMLの解析以外に、取得先のサーバーやデータの利用条件も考える必要があります。

取得先の利用条件を確認する

Webページを機械的に取得する前に、利用規約、提供されているAPI、データの利用条件、robots.txtの内容を確認します。robots.txtだけで利用可否を判断せず、取得先が用意している正式なAPIやデータ提供方法があれば、そちらを優先しましょう。

アクセス頻度とキャッシュを考える

短い間隔で同じページを何度も取得すると、相手側サーバーへ負荷をかける可能性があります。取得件数や実行頻度を抑え、必要に応じてキャッシュを使います。定期的に取得する場合は、更新日時やETagなどを利用できるかも確認します。

タイムアウトと取得サイズを制限する

外部サーバーが応答しない場合にPHPの処理が長時間待ち続けないよう、接続と読み込みのタイムアウトを設定します。想定外に大きなレスポンスを保存・解析しないよう、取得サイズの上限も決めます。

User-Agentと連絡先を明示する

取得処理を運用する場合は、どのアプリケーションからのアクセスか分かるUser-Agentを設定し、必要に応じて連絡先を示します。相手側から停止や頻度変更の依頼があった場合に対応できるよう、取得処理の実行元と停止方法も管理しておきます。

利用者が指定したURLを取得する場合

利用者が入力したURLをサーバーから取得する機能では、SSRFにも注意が必要です。内部ネットワークやクラウドのメタデータサービスへ接続されないよう、許可するホストを限定し、リダイレクト先、ポート、取得サイズ、タイムアウトを確認します。

外部URLから取得したHTMLに含まれる文章や画像を、自由に再利用できるとは限りません。著作権、データ提供元の規約、個人情報、表示時の出典なども確認した上で利用します。

まとめ

PHP 8.3でHTMLファイルの一覧表を解析するときは、DOMDocumentへHTMLを読み込み、DOMXPathで対象のtableやtrを検索します。各行からDOMNodeのtextContentを取得し、博物館名と住所の配列へ変換できます。

HTMLが不正な場合は、解析に成功したことと、期待した要素を取得できたことを別々に確認します。日本語を扱う場合は、HTMLの保存形式や文字コードも確認します。

外部URLへ対象を広げる場合は、HTMLの解析方法だけでなく、取得先の利用条件、アクセス頻度、キャッシュ、タイムアウト、取得サイズ、SSRF対策まで含めて設計することが大切です。

参考資料

この記事を書いた人

※上が私です。

奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。

これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。

【制作会社・企業様向けサポート】
  • 専任エンジニアのいない企業様に対するシステム面の不安を解消
  • 柔軟な契約形態や短納期での対応により、急なニーズにも迅速にサポート
  • システムの企画段階から運用まで、ワンストップでのサービスを提供

Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。

ぜひ、プロフィールやWeb制作会社様向け業務案内、一般企業様向け業務案内もご覧くださいね。

PHPで外部APIのフォールバックとキャッシュを実装する:該当なしと通信失敗を分けて扱う

2026.10.23

外部APIの「該当なし」と通信失敗を区別し、代替APIへ切り替える条件と取得結果のキャッシュをPHPで実装します。架空の商品情報APIを使い、問い合わせ回数・待ち時間の制限、キャッシュの有効期限・無効化、モックによる動作確認を解説します。

PHP

Laravelのコードレビューで遅いSQLの候補を洗い出す

2026.10.02

Laravelのコードレビューで、ループ内のDBアクセスやEloquentのN+1、大量取得、検索条件とインデックスなど、性能問題につながりやすい箇所を候補として洗い出します。Laravelで発行SQLと実行時間を確認し、MySQLのEXPLAINや実データに近い環境での計測へつなげる方法を紹介します。コードレビューだけではSQLの実行時間を断定できない点も説明します。

Laravel MySQL PHP

Windows 11でDocker Composeを使ってLaravel環境を作る:Container・Volume・Networkの基本

2026.10.01

Windows 11のDocker Desktop上に、DockerfileとDocker Composeを使ってLaravel・PHP・MySQL・Mailpitの開発環境を構築します。ImageとContainerの関係、Bind MountとNamed Volumeによるデータの保存、ポート公開とContainer間通信の違いを実例で確認。LaravelからMySQLへ接続するときに、localhostではなくService名を使う理由も説明します。

Docker Laravel PHP

PHPでYAML設定を検証する:symfony/yamlとopis/json-schemaの使い方

2026.09.30

symfony/yamlでYAMLを読み込み、opis/json-schemaで必須項目や型、許可する値を検証する方法を紹介します。YAMLのマップをstdClassとして扱う方法や、空のobjectとarrayの違い、信頼できないYAMLを扱う際の注意点も解説します。

PHP YAML

阿部辰也へのお仕事の依頼・お問い合わせ

軽いご相談もお気軽にどうぞ!

個人情報の取り扱いについて *必須 プライバシーポリシーをご確認いただき、同意いただける場合は「同意する」にチェックをしてください。

keyboard_double_arrow_up
TOP