PHP 8.3でHTMLのtableを解析する:DOMDocumentとDOMXPathで一覧データを抽出
作成日:2026.09.05
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でメールOTPによる追加認証を実装する:発行・保存・送信・検証の設計
2026.09.04
PHP 8.3以上を対象に、パスワードログイン後にメールOTPで追加認証を行う処理を、フレームワークに依存しない形で整理します。コードの生成・保存・送信・検証、期限切れや再送、試行回数制限を扱い、メールOTPをTOTPやパスキーと同等に扱わないための注意点も説明します。
Laravel 13で既存のテーブルを使ってログイン機能を実装する
2026.09.02
Laravel 13の既存連載で作成したmembersテーブルを認証用ユーザーとして流用し、Memberモデルと認証プロバイダーを設定します。SQLiteに保存したユーザー情報を使ったメールアドレス・パスワード認証、ログイン済み画面の表示、データベースセッション、ログアウト時のセッション破棄までを実装します。
PHP 8.3のcURLでISBNから書誌情報を取得する:openBD・Google Books API・NDLサーチ
2026.09.01
PHP 8.3のcURLを使い、ISBNをもとにopenBD、Google Books API、NDLサーチへアクセスする実装例を紹介します。JSON/XMLの解析、書誌情報の共通形式への変換、通信エラーや項目欠損の扱いに加え、openBD v1の終了予定やNDLサーチの利用条件も整理します。
Laravel 13+Vue.jsで画像・CSVを非同期アップロードする:FormData・ファイル検証・非公開保存
2026.08.31
Laravel 13のBladeに組み込んだVue.jsから、FormDataを使って画像またはCSVを非同期で送信する方法を解説します。JPEG・PNGの画像検証、CSVの文字コード変換と簡易的な内容確認、20MBのサイズ制限、非公開ディスクへの保存、CSRF・バリデーションエラー処理を実装し、fetchを基本にaxiosとの違いも整理します。