【PHP実践|実務向け】PHPでのHTML解析:DOMDocumentとXPathを活用した効率的なスクレイピング手法

導入

Web開発の現場において、外部サイトの情報を取得したり、特定のHTML構造からデータを抽出する「スクレイピング」は非常に重要なスキルです。正規表現を使ってHTMLをパースしようとすると、タグの入れ子構造や属性の順序変更などにより、すぐにメンテナンス不可能なコードになってしまいます。PHPでは、標準搭載されているDOMDocumentとDOMXPathを組み合わせることで、堅牢かつ柔軟なHTML解析が可能になります。

基礎知識

DOMDocumentは、HTMLやXMLをメモリ上にツリー構造(DOM)として展開するためのクラスです。HTMLを読み込み、要素や属性をオブジェクトとして操作できます。
一方、DOMXPathは、そのDOMツリーの中から特定の要素をクエリ言語(XPath)を使って検索するためのツールです。CSSセレクタに近い感覚で、「idがcontentのdivの中にあるpタグ」といった複雑な指定を簡潔に記述できます。

実装/解決策

HTMLを解析する際の基本的なステップは以下の通りです。
1. 読み込み: loadHTML()を使用してHTML文字列を読み込みます。HTMLの構造が不完全な場合のエラーを抑制するため、メソッド呼び出しの前に「@」を付与するのが一般的です。
2. XPathの初期化: DOMDocumentオブジェクトをDOMXPathのコンストラクタに渡し、検索エンジンを準備します。
3. クエリ実行: query()メソッドを用いて対象要素を抽出します。戻り値はDOMNodeListオブジェクトとなるため、item(0)で個別のノードにアクセスします。

サンプルプログラム

以下は、特定のdiv要素内の内容を取得し、そこからさらに属性や子要素を抽出する実用的なコード例です。

データ1

データ2

‘;

// 1. DOMDocumentの初期化とHTMLの読み込み
$dom = new DOMDocument(‘1.0’, ‘UTF-8’);
// 文字化け防止のためエンコーディングをHTML-ENTITIESに変換
$html = mb_convert_encoding($html, “HTML-ENTITIES”, ‘UTF-8’);
@$dom->loadHTML($html);

// 2. XPathオブジェクトの作成
$xpath = new DOMXPath($dom);

// 3. 目的のノードを取得(id=”content”内の最初のpタグ)
$nodes = $xpath->query(‘//div[@id=”content”]/p[1]’);

if ($nodes->length > 0) {
$node = $nodes->item(0);
// テキスト内容の取得
echo “テキスト: ” . $node->nodeValue . “\n”;
// class属性の取得
echo “クラス名: ” . $node->getAttribute(‘class’) . “\n”;
}

// 4. HTML構造ごと取得したい場合
function getInnerHtml(DOMNode $node) {
$innerHTML = ”;
foreach ($node->childNodes as $child) {
$innerHTML .= $node->ownerDocument->saveHTML($child);
}
return $innerHTML;
}
?>

応用・注意点

現場で活用する際に注意すべきポイントを3点挙げます。

1. インデックスの開始位置: XPathのインデックスは「1」から始まります。DOMNodeListのitem()メソッドは「0」から始まるため、混同しないように注意してください。
2. HTML文字参照: DOMDocumentは読み込み時にHTMLエンティティを適切に処理しますが、意図しない文字化けが発生する場合は、loadHTML時に文字コードを明示するか、mb_convert_encodingでHTML-ENTITIESへ変換してください。
3. 柔軟なセレクタ: class属性が複数ある場合、単純な属性指定では抽出できません。contains()関数を使って「[contains(@class, ‘target’)]」のように記述することで、部分一致による柔軟な検索が可能になります。

HTMLは常に不規則なデータが送られてくる可能性があるため、常にノードの存在チェック(lengthの確認など)を行い、例外的な構造にも対応できる設計を心がけましょう。

タイトルとURLをコピーしました