HTML

HTMLパーサ

PHP Simple HTML DOM Parser

PHP Simple HTML DOM Parserは非公式のライブラリであり、ダウンロードしたライブラリをインクルードして使用します。

DOMDocument

DOMDocumentは、PHPのDOM拡張モジュールのクラスです。

bool DOMDocument::loadHTML ( string $source )
PHP: DOMDocument::loadHTML - Manual

文字列$sourceに含まれるHTMLをパースします。

bool DOMDocument::loadHTMLFile ( string $filename )
PHP: DOMDocument::loadHTMLFile - Manual

ファイル$filenameから読み込んだHTMLドキュメントをパースします。

loadHTML()で誤ってHTMLファイルを読み込もうとすると、引数の「ファイル名」をHTMLコードとしてパースします。そのときには警告も表示されないため注意が必要です。なおXMLファイルから読み込むメソッドはload()であるため、これと誤解しないようにします。

特定の要素の取得

HTMLファイルを読み込み、DOMXPathでimg要素を取得するには、

$dom = new DOMDocument();
@$dom->loadHTMLFile( $filename );

$xpath = new DOMXpath( $dom );
$elem = $xpath->query( '//img' );

とします。同様の処理をSimpleXMLでもできます。

$dom = new DOMDocument();
@$dom->loadHTMLFile( $filename );

$xml = simplexml_import_dom( $dom );
$elem = $xml->xpath( '//img' );
DOMXpathとSimpleXMLの相違

HTMLコードに非標準のタグが、XMLの規約に反する形式で含まれているとき、DOMXpathとSimpleXMLでは異なる結果を返します。

たとえばwbr要素について考えてみます。

$str = '<div>A<wbr>B</div>';

$dom = new DOMDocument();
@$dom->loadHTML( $str );

DOMXpathの場合、

$xml = new DOMXpath( $dom );
$result = $xml->query( '/*' );

print_r( $result->item( 0 ) );
// DOMElement Object
// (
//     [tagName] => html
//     [schemaTypeInfo] =>
//     [nodeName] => html
//     [nodeValue] => AB
//     [nodeType] => 1
//     ... 省略
//     [localName] => html
//     [baseURI] =>
//     [textContent] => AB
// )

SimpleXMLの場合、

$xml = simplexml_import_dom( $dom );
$result = $xml->xpath( '/*' );

print_r( $result[ 0 ] );
// SimpleXMLElement Object
// (
//     [body] => SimpleXMLElement Object
//         (
//             [div] => A
//         )
// )

このとき<wbr>ではなく、<wbr />のようにXMLの規約に従う形式ならば相違は生じません。

文字エンコーディングの相違による問題

loadHTMLFile()によるHTMLファイルの読み込み時に、input conversion failed due to input errorと警告されることがあります。これは、

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

による文字エンコーディングの指定と、実際の文字エンコーディングが異なっている状態で発生します。このときにはファイルから文字列として読み込み、文字エンコーディングを変換してからloadHTML()で処理します。

自力

ファイル関数を使用してファイルから文字列として読み込み、それを文字列の関数を使用して解析します。

たとえばtitleタグの要素を取得するならば、次のようにします。

function GetTitleTag( $url )
{
    $result = null;

    $filePointer = fopen( $url, 'r' );
    if( $filePointer )
    {
        while( !feof( $filePointer ) )
        {
            // ファイルポインタから1行取得する
            $text = fgets( $filePointer );

            if( strpos( $text, '<title' ) !== FALSE )
            {
                $pattern = '/<title.*?>(.*?)<\/title>/i';
                if( preg_match( $pattern, $text, $matches ) )
                {
                    $to_encoding = 'UTF-8';
                    $from_encoding = 'EUC-JP, UTF-8, JIS, SJIS, ASCII';

                    $result = mb_convert_encoding( $matches[ 1 ], $to_encoding, $from_encoding );
                }
                break;
            }
        }

        fclose( $filePointer );
    }

    return $result;
}

このコードには、次の問題点があります。

  • <title>内に改行コードが含まれていると、取得に失敗します。
  • あらかじめページから改行コードが取り除かれていると、file_get_contents()と比較しても高速にはなりません。