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.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() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正

解説:初期化処理

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)が詳しい。

参考サイト

(この項おわり)
header