PHPでDOMDocumentを使ってスクレイピング

(1/1)
Webサイトから情報を抽出することをスクレイピング(scraping)と呼ぶ。いままで紹介してきたPHPプログラムでは、正規表現などを使ってスクレイピングを行ってきた。
PHPバージョン5以上では、HTMLやXMLドキュメントをオブジェクトとして扱うことができる DOMDocumentクラスが用意されている。
そこで今回は、DOMDocumentクラスを使ってスクレイピングを行ってみることにする。他のプログラムでも利用できるよう、DOMDocumentクラスを利用する形のユーザー・クラス pahooScraping を用意した。

(2024年10月31日)__construct() 文字化け対策pahooScrapingクラスに変更
(2024年9月29日)getValueFistrXPath() 属性値でない指定に対応

サンプル・プログラムの実行例

PHPでDOMDocumentを使ってスクレイピング

目次

サンプル・プログラム

圧縮ファイルの内容
DOMscraping.phpサンプル・プログラム本体。
pahooScraping.phpスクレイピング処理に関わるクラス pahooScraping。
スクレイピング処理に関わるクラスの使い方は「PHPで作るRSSビューア」を参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
DOMscraping.php 更新履歴
バージョン 更新日 内容
4.0.1 2023/09/29 bug-fix
2.0.0 2023/09/18 pahooScrapingクラスに変更
1.1 2021/05/08 PHP8対応,リファラ・チェック改良
1.0 2015/10/20
pahooScraping.php 関数/メソッド一覧
関数/メソッド 機能 詳細
class::pahooScraping
__construct コンストラクタ
__destruct デストラクタ
isJapanese 使用言語が日本語かどうかを判定する
setLanguage 使用言語を設定する.
isError エラーが発生しているかどうかを取得する.
getError エラー番号を取得する.
getErrorMessage エラー・メッセージを取得する.
resetError エラー状態をリセットする.
isphp8over PHP8以上かどうか検査する
evalXPath 指定したXPath式を評価し,結果を返す.
queryXPath 指定したXPath式を評価する.
getTitle titleを取得
getDescription descriptionを取得
getValueFistrXPath 指定したXPath式に合致する最初のタグの値もしくは属性値を返す.
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.8.1 2025/03/15 validRegexPattern() -- debug
1.8.0 2024/11/12 validRegexPattern() 追加
1.7.0 2024/10/09 validURL() validEmail() 追加
1.6.0 2024/10/07 isButton() -- buttonタグに対応
1.5.0 2024/01/28 exitIfExceedVersion() 追加

解説:初期化処理

DOMscraping.php

  29: //プログラム・タイトル
  30: define('TITLE', 'DOMDocumentを使ってスクレイピング');
  31: 
  32: //データ入力に関わる関数群:include_pathに配置すること
  33: require_once('pahooInputData.php');
  34: 
  35: //PHPバージョン・チェック
  36: exitIfLessVersion(MINUMUM_VERSION);
  37: 
  38: //リファラチェック+リリースフラグの設定
  39: if (isset($_SERVER['HTTP_HOST']) && ($_SERVER['HTTP_HOST'] == 'localhost')) {
  40:     define('FLAG_RELEASE', FALSE);
  41:     define('REFER_ON', '');
  42:     ini_set('display_errors', 1);
  43:     ini_set('error_reporting', E_ALL);
  44: else {
  45:     //リリース・フラグ(公開時にはTRUEにすること)
  46:     define('FLAG_RELEASE', TRUE);
  47:     //リファラ・チェック(直リン防止用;空文字ならチェックしない)
  48:     define('REFER_ON', 'www.pahoo.org');
  49: }
  50: 
  51: //表示幅(ピクセル)
  52: define('WIDTH', 600);
  53: 
  54: //スクレイピングURL(デフォルト)
  55: define('DEF_URL', 'https://www.pahoo.org/e-soul/webtech/php02/php02-41-01.shtm');
  56: 
  57: //スクレイピング処理に関わるクラス:include_pathが通ったディレクトリに配置
  58: require_once('pahooScraping.php');

各種定数・変数は、変更不可という記載がないかぎり、自由に変更可能である。

関数  require_once  を使って、スクレイピング処理を行うクラス pahooScraping.php を読み込む。include_pathが通ったディレクトリに配置しておくこと。

解説:pahooScrapingクラスとDOMDocumentクラスの使い方

pahooScraping.php

  13: class pahooScraping {
  14:     private $dom;           //DOMDocument
  15:     private $xpath;         //DOMXPath
  16:     private $language;      //使用言語(初期値は日本語)
  17:     private $error;         //エラー・メッセージ
  18:     private $errorTable;    //エラー番号 => エラー・メッセージ 一覧
  19: 

pahooScraping.php

  20: /**
  21:  * コンストラクタ
  22:  * @param   string $contents 解析対象テキスト(UTF-8)
  23:  * @return  object /NULL:PHP8未満のため実行できない.
  24: */
  25: function __construct($contents) {
  26:     //PHPのバージョンをチェックする.
  27:     if (! $this->isphp8over()) {
  28:         $this->error = 111;
  29:         throw new Exception($this->getErrorMessage());
  30:         return NULL;
  31:     }
  32:     //プロパティの初期値を設定する.
  33:     try {
  34:         // Unicodeの範囲を指定
  35:         $map = [ 0x80, 0xFFFF, 0, 0xFFFF ];
  36:         // DOMの文字化け対策:UTF-8文字を数値エンティティに変換
  37:         $contents = mb_encode_numericentity($contents, $map, 'UTF-8');
  38:         // DOMDocument クラス
  39:         $this->dom = new DOMDocument();
  40:         libxml_use_internal_errors(TRUE);
  41:         @$this->dom->loadHTML($contents);
  42:         libxml_clear_errors();
  43:         $this->xpath = new DOMXPath($this->dom);
  44:     } catch (Exception $e) {
  45:         $this->error = 121;
  46:         throw new Exception($e->getMessage());
  47:         return FALSE;
  48:     }
  49: 
  50:     //エラー番号 => エラー・メッセージ 一覧
  51:     $this->errorTable = array(
  52:         111 => array('PHP8以上が必要です', 'PHP8 or higher is required'),
  53:         121 => array('DOMエラーです', 'DOM error'),
  54:         999 => array('不明のエラー', 'unknown error'),
  55:     );
  56: }

PHPでクラスを使ってテキストの読みやすさを調べる」で簡単なクラスの作り方を紹介したが、今回は、スクレイピングを行うユーザー定義クラス pahooScraping を用意し、その中で最初に呼ばれるメソッド「コンストラクタ」を使って DOMDocumentクラス を用意することにする。
まず、pahooScrapingg クラス内で使用するプロパティ $dom, $xpath, $error を宣言する。

コンストラクタは、__construct によって定義する。引数は、クラスを呼び出した時の引数である。ここでは解析対象のテキストを引数とする。UTF-8でエンコードしたテキストを渡すこと。戻り値はない。

次に DOMDocumentクラスの使い方だが、その前に DOM について簡単に説明しておこう。
DOM とは Document Object Model の略で HTMLXML 文書をオブジェクトとして扱えるようにしたモデルのことだ。モデルというと分かりにくいのだが、要するに、HTMLやXMLをツリー構造(階層構造)のデータとして扱うことができるようになり、タグや属性名を指定して、目的のコンテンツを読み込んだり編集したりする手段を提供してくれる。
たとえば、<title> タグの内容を取得したり、<a> タグで指し示すリンクを全て取得する、といった処理が簡単にできるようになる。スクレイピングするのに、打って付けの仕組みだ。

2024年(令和6年)10月現在、DOMDocumentクラス にコンテンツを投入するとき、エンコード指定より前に日本語などUTF-8文字があると文字化けが起きることがわかっている。そこで、投入する前に、 mb_encode_numericentity 関数を使って、UTF-8文字を数値エンティティ(HTMLエンティティ)に変換しておく。

コンストラクタで DOMDocument インスタンスを生成したら、DOMDocument::loadHTML を使って文字列からHTMLを読み込む。
DOM のエラー(例外)発生時に対応して、例外を返すようにしている。

ここで、文字コードに注意しなければならない。
DOMDocument::loadHTML は、HTML文書に記載されている文字コードセットを解釈しないようで、UTF-8などの日本語文字が文字化けを起こす。そこで、DOMDocument::loadHTML に渡す前に、関数  mb_convert_encoding  を使って HTML-ENTITIES にエンコードしてやる。こうすることで、日本語文字などは16進数に変換されるので、文字化けを起こすことはなくなる。

pahooScraping.php

  58: /**
  59:  * デストラクタ
  60:  * @return  なし
  61: */
  62: function __destruct() {
  63:     unset($this->dom);
  64:     unset($this->xpath);
  65: }

クラスを解放する時にコールされるデストラクタを、念のために用意しておく。デストラクタは __destruct によって定義する。

解説:XPath式の使い方

pahooScraping.php

 134: /**
 135:  * 指定したXPath式を評価し,結果を返す.
 136:  * @param   string $e XPath式
 137:  * @return  DOMNodeList/FALSE:評価失敗
 138: */
 139: function evalXPath($e) {
 140:     return $this->error ? FALSE : $this->xpath->evaluate("string({$e})");
 141: }

pahooScraping.php

 152: /**
 153:  * titleを取得
 154:  * @return  titleの内容
 155: */
 156: function getTitle() {
 157:     return $this->evalXPath('/html/head/title');
 158: }

pahooScraping.php

 160: /**
 161:  * descriptionを取得
 162:  * @return  descriptionの内容
 163: */
 164: function getDescription() {
 165:     return $this->evalXPath('/html/head/meta[@name="description"]/@content');
 166: }

読み込んだHTML文書を検索する方法はいくつかあるが、ここでは、ノードから検索する XPath式を用いることにする。
PHPでは、DOMXPath クラスを使う。コンストラクタで、DOMXPath でオブジェクトを生成するところまでは実行している。
次に、DOMXPath::evaluate を使ってXPath式の評価(検索)をしていく。そのためのメソッド evalXPath を用意した。

titleタグの内容を取り出すメソッド getTitle と、'meta name="description" content=' の内容を取り出すメソッド getDescription を用意した。

ここで、XPath式 の書き方のエッセンスを紹介する。
XPath式は、HTML文書やXML文書をツリー構造として扱う。ツリーのノードとなるのはタグである。XPath式 を活用することで、HTML文書やXML文書からターゲット情報を簡単に取り出すことができる。

'/' は階層をあらわす。'/html/head/title]' は、「htmlタグ→headタグ→titleタグ」にマッチする。これに DOMXPath::evaluate を適用すると、titleタグの内容を取得できる。

'//' はドキュメント全体をあらわす。'//h2' は、bodyタグの直下にあるh2タグや、divタグで囲まれた内側にあるh2タグなど、どの階層のh2タグにもマッチする。

タグの中にある属性を示すには '@' を使う。たとえば '//a/@href' はあらゆる階層にあるaタグのhrefの値(=ハイパーリンク)にマッチする。
また、'//meta[@name="description"]/@content' は、あらゆる階層にある 「meta name="description"」タグの属性contentにマッチする。

述語 contains は、特定の文字列が含まれる要素にマッチする。たとえば //p[contains(text(), ‘PHP’)] は、あらゆる階層にあるpタグのうち「PHP」を含むpタグにマッチする。

PHPには XPath式を評価するメソッドとして、DOMXPath::evaluateDOMXPath::query の2種類がある。
DOMXPath::evaluateXPath式の評価結果によって返る値が異なる。たとえば '/html/head/title]' はマッチするtitleは1つだけのはずなので、titleタグで囲まれた文字列が返される。一方、'//h2' は複数あれば、DOMNodeList が返される。1つ1つのノードを調べたいときには、foreach文を使う。
DOMXPath::queryXPath式の評価結果が1つであろうが複数あろうが、常に DOMNodeList を返す。

XPath式の書き方は「【超便利!】XPathの基本や書き方まで、Webスクレイピング初心者にも分かりやすく解説」(JITERA)が詳しい。

解説:HTTP通信とリクエストライン

スクレイピングするには、まず、コンテンツを取得する必要がある。PHPでは組み込み関数 [file_get_contents:php_functioin] を使ってWebコンテンツを取得することができるはずだが、エラーを発生するサイトもある。なぜか――そこで、HTTP通信の仕組みを詳しく見ていくことにしよう

たとえば、「PHPで天気予報を求める」のサンプル・プログラムをクリックすると、ブラウザから次のようなデータがぱふぅ家のホームページのWebサーバ(HTTPサーバ)に向かって送信される。これを HTTPリクエスト と呼ぶ。
GET /e-soul/webtech/php06/program/jmaWeeklyWeather.php HTTP/1.1\r\n
Host: www.pahoo.org\r\n
Connection: keep-alive\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n
Accept-Encoding: gzip, deflate, br, zstd\r\n
Accept-Language: ja,en-US;q=0.8,en;q=0.6\r\n
Priority: u=0, i\r\n
Referer: https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36\r\n
\r\n
forecast=1®ion=03&pref=13&station=44132&start=0&goal=6
1行目をリスクエストライン、2行目から14行目の空行(\r\n)までを HTTPリクエストヘッダ と呼ぶ。

リスクエストラインには、GETメソッドPOSTメソッドかの識別と、呼び出すコンテンツのパス名とファイル名、HTTP通信のバージョン(1.1)が入る。
GETメソッドの場合、空行(\r\n)の次の行に送信パラメータが並んでいる。POSTメソッドについては後述する。

HTTP通信のバージョンは、2025年(令和7年)5月現在、下記の通り。現在、大多数の HTTP通信は HTTP/1.1 である。
リリース年バージョン特徴
1990年 HTTP/0.9 プロトタイプ。HTMLコンテンツを取得するだけ。
1996年 HTTP/1.0 リクエストヘッダ、レスポンスヘッダが加わった。
HTTPのバージョンが加わった。
リクエスト時にメソッド(GET、PUT、POST、DELETE等)が送信できる。
レスポンス時にステータスコードが返る。
1997年 HTTP/1.1 通信の高速化
TLSをサポート
チャンクエンコーディングをサポート
2015年 HTTP/2 テキストベースからバイナリベースへ変更
ストリームを使ったデータの送受信に対応
サーバープッシュをサポート
ヘッダの圧縮
2018年 HTTP/3 QUICと呼ばれるUDPソケットを用いたプロトコル上で動作
ヘッダーの圧縮方式をQPACKに変更
より早いハンドシェイク
全ての通信をTLS1.3によって暗号化

解説:HTTPリクエストヘッダ

前述の通り、HTTP/1.0HTTP/1.1 における HTTPリクエストヘッダは、
フィールド名: 内容\r\n
というテキストが複数行続く。主なフィールド名とその設定値について一覧表に整理する。
フィールド名設定内容
Host 接続先ホスト名
Connection 現在のトランザクションが完了したあとも、ネットワーク接続を開いたままにするかどうかを制御する。HTTP/1.0では "close" がデフォルトで、接続を閉じる。HTTP/1.1では "Keep-Alive" がデフォルトで、接続を閉じない。
Accept クライアントが処理できるコンテンツタイプをMIMEタイプで示す。
Accept-Encoding クライアントが処理できるコンテンツのエンコーディング(圧縮アルゴリズム)を示す。
Accept-Language クライアントが処理できる言語、ロケールを示す。
Priority 要求されたリソースを含むレスポンスを、同じ接続上の他のリソースリクエストと比較して、どの優先順位で送信するかを示す。uの値は0から7までの整数で、小さいほど優先順位が高い。incremental(i)ディレクティブが存在する場合、HTTP応答を増分的に処理できることを示す。
Referer 直前のウェブページのアドレスを示す。
User-Agent サーバーやネットワークピアがアプリケーション、オペレーティングシステム、ベンダーや、リクエストしているユーザーエージェントのバージョン等を識別する文字列が入る。

参考サイト

(この項おわり)
header