PHP Simple HTML DOM Parser

PHP Simple HTML DOM Parserを利用することで、HTMLの要素へ簡単にアクセスできるようになります。

導入

まずライブラリをダウンロードします。

PHP Simple HTML DOM Parser - Browse Files at SourceForge.net

そして、それを読み込みます。

require_once 'simple_html_dom.php';

オブジェクトの生成

ヘルパー関数のstr_get_html()またはfile_get_html()から、simple_html_domオブジェクトを生成します。

// 文字列から
$html = str_get_html( '<html><body>Hello!</body></html>' );

// URLから
$html = file_get_html( 'http://example.com/' );

// HTMLファイルから
$html = file_get_html( 'sample.htm' );

たとえば、文字列'<html><body>Hello!</body></html>'から生成したオブジェクトは、次のような内容となります。(長くなりすぎるため、一部省略してあります)

simple_html_dom Object
(
  [root] => simple_html_dom_node Object
    (
      [nodetype] => 5
      [tag] => root
      [attr] => Array ()
      [children] => Array
        (
          [0] => simple_html_dom_node Object
            (
              [nodetype] => 1
              [tag] => html
              [attr] => Array ()
              [children] => Array
                (
                  [0] => simple_html_dom_node Object
                    (
                      [nodetype] => 1
                      [tag] => body
                      [attr] => Array ()
                      [children] => Array ()
                      [nodes] => Array
                        (
                          [0] => simple_html_dom_node Object
                            (
                              [nodetype] => 3
                              [tag] => text
                              [attr] => Array ()
                              [children] => Array ()
                              [nodes] => Array ()
                              [parent] => simple_html_dom_node Object
 *RECURSION*
                              [_] => Array
                                (
                                  [4] => Hello!
                                )

                              [tag_start] => 0
                              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                            )

                        )

                      [parent] => simple_html_dom_node Object
 *RECURSION*
                      [_] => Array
                        (
                          [0] => 2
                          [7] =>
                          [1] => 4
                        )

                      [tag_start] => 6
                      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                    )

                )

              [nodes] => Array
                (
                  [0] => simple_html_dom_node Object
                    (
                      [nodetype] => 1
                      [tag] => body
                      [attr] => Array ()
                      [children] => Array ()
                      [nodes] => Array
                        (
                          [0] => simple_html_dom_node Object
                            (
                              [nodetype] => 3
                              [tag] => text
                              [attr] => Array ()
                              [children] => Array ()
                              [nodes] => Array ()
                              [parent] => simple_html_dom_node Object
 *RECURSION*
                              [_] => Array
                                (
                                  [4] => Hello!
                                )

                              [tag_start] => 0
                              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                            )

                        )

                      [parent] => simple_html_dom_node Object
 *RECURSION*
                      [_] => Array
                        (
                          [0] => 2
                          [7] =>
                          [1] => 4
                        )

                      [tag_start] => 6
                      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                    )

                )

              [parent] => simple_html_dom_node Object
 *RECURSION*
              [_] => Array
                (
                  [0] => 1
                  [7] =>
                  [1] => 4
                )

              [tag_start] => 0
              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
            )

        )

      [nodes] => Array
        (
          [0] => simple_html_dom_node Object
            (
              [nodetype] => 1
              [tag] => html
              [attr] => Array ()
              [children] => Array
                (
                  [0] => simple_html_dom_node Object
                    (
                      [nodetype] => 1
                      [tag] => body
                      [attr] => Array ()
                      [children] => Array ()
                      [nodes] => Array
                        (
                          [0] => simple_html_dom_node Object
                            (
                              [nodetype] => 3
                              [tag] => text
                              [attr] => Array ()
                              [children] => Array ()
                              [nodes] => Array ()
                              [parent] => simple_html_dom_node Object
 *RECURSION*
                              [_] => Array
                                (
                                  [4] => Hello!
                                )

                              [tag_start] => 0
                              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                            )

                        )

                      [parent] => simple_html_dom_node Object
 *RECURSION*
                      [_] => Array
                        (
                          [0] => 2
                          [7] =>
                          [1] => 4
                        )

                      [tag_start] => 6
                      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                    )

                )

              [nodes] => Array
                (
                  [0] => simple_html_dom_node Object
                    (
                      [nodetype] => 1
                      [tag] => body
                      [attr] => Array ()
                      [children] => Array ()
                      [nodes] => Array
                        (
                          [0] => simple_html_dom_node Object
                            (
                              [nodetype] => 3
                              [tag] => text
                              [attr] => Array ()
                              [children] => Array ()
                              [nodes] => Array ()
                              [parent] => simple_html_dom_node Object
 *RECURSION*
                              [_] => Array
                                (
                                  [4] => Hello!
                                )

                              [tag_start] => 0
                              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                            )

                        )

                      [parent] => simple_html_dom_node Object
 *RECURSION*
                      [_] => Array
                        (
                          [0] => 2
                          [7] =>
                          [1] => 4
                        )

                      [tag_start] => 6
                      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                    )

                )

              [parent] => simple_html_dom_node Object
 *RECURSION*
              [_] => Array
                (
                  [0] => 1
                  [7] =>
                  [1] => 4
                )

              [tag_start] => 0
              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
            )

        )

      [parent] =>
      [_] => Array
        (
          [0] => -1
          [1] => 4
        )

      [tag_start] => 0
      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
    )

  [nodes] => Array
    (
      ... omitted
    )

  [callback] =>
  [lowercase] => 1
  [original_size] => 32
  [size] => 32
  [pos:protected] => 32
  [char:protected] =>
  [cursor:protected] => 4
  [parent:protected] => simple_html_dom_node Object
    (
      [nodetype] => 5
      [tag] => root
      [attr] => Array ()
      [children] => Array
        (
          [0] => simple_html_dom_node Object
            (
              [nodetype] => 1
              [tag] => html
              [attr] => Array ()
              [children] => Array
                (
                  [0] => simple_html_dom_node Object
                    (
                      [nodetype] => 1
                      [tag] => body
                      [attr] => Array ()
                      [children] => Array ()
                      [nodes] => Array
                        (
                          [0] => simple_html_dom_node Object
                            (
                              [nodetype] => 3
                              [tag] => text
                              [attr] => Array ()
                              [children] => Array ()
                              [nodes] => Array ()
                              [parent] => simple_html_dom_node Object
 *RECURSION*
                              [_] => Array
                                (
                                  [4] => Hello!
                                )

                              [tag_start] => 0
                              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                            )

                        )

                      [parent] => simple_html_dom_node Object
 *RECURSION*
                      [_] => Array
                        (
                          [0] => 2
                          [7] =>
                          [1] => 4
                        )

                      [tag_start] => 6
                      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                    )

                )

              [nodes] => Array
                (
                  [0] => simple_html_dom_node Object
                    (
                      [nodetype] => 1
                      [tag] => body
                      [attr] => Array ()
                      [children] => Array ()
                      [nodes] => Array
                        (
                          [0] => simple_html_dom_node Object
                            (
                              [nodetype] => 3
                              [tag] => text
                              [attr] => Array ()
                              [children] => Array ()
                              [nodes] => Array ()
                              [parent] => simple_html_dom_node Object
 *RECURSION*
                              [_] => Array
                                (
                                  [4] => Hello!
                                )

                              [tag_start] => 0
                              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                            )

                        )

                      [parent] => simple_html_dom_node Object
 *RECURSION*
                      [_] => Array
                        (
                          [0] => 2
                          [7] =>
                          [1] => 4
                        )

                      [tag_start] => 6
                      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                    )

                )

              [parent] => simple_html_dom_node Object
 *RECURSION*
              [_] => Array
                (
                  [0] => 1
                  [7] =>
                  [1] => 4
                )

              [tag_start] => 0
              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
            )

        )

      [nodes] => Array
        (
          [0] => simple_html_dom_node Object
            (
              [nodetype] => 1
              [tag] => html
              [attr] => Array ()
              [children] => Array
                (
                  [0] => simple_html_dom_node Object
                    (
                      [nodetype] => 1
                      [tag] => body
                      [attr] => Array ()
                      [children] => Array ()
                      [nodes] => Array
                        (
                          [0] => simple_html_dom_node Object
                            (
                              [nodetype] => 3
                              [tag] => text
                              [attr] => Array ()
                              [children] => Array ()
                              [nodes] => Array ()
                              [parent] => simple_html_dom_node Object
 *RECURSION*
                              [_] => Array
                                (
                                  [4] => Hello!
                                )

                              [tag_start] => 0
                              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                            )

                        )

                      [parent] => simple_html_dom_node Object
 *RECURSION*
                      [_] => Array
                        (
                          [0] => 2
                          [7] =>
                          [1] => 4
                        )

                      [tag_start] => 6
                      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                    )

                )

              [nodes] => Array
                (
                  [0] => simple_html_dom_node Object
                    (
                      [nodetype] => 1
                      [tag] => body
                      [attr] => Array ()
                      [children] => Array ()
                      [nodes] => Array
                        (
                          [0] => simple_html_dom_node Object
                            (
                              [nodetype] => 3
                              [tag] => text
                              [attr] => Array ()
                              [children] => Array ()
                              [nodes] => Array ()
                              [parent] => simple_html_dom_node Object
 *RECURSION*
                              [_] => Array
                                (
                                  [4] => Hello!
                                )

                              [tag_start] => 0
                              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                            )

                        )

                      [parent] => simple_html_dom_node Object
 *RECURSION*
                      [_] => Array
                        (
                          [0] => 2
                          [7] =>
                          [1] => 4
                        )

                      [tag_start] => 6
                      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
                    )

                )

              [parent] => simple_html_dom_node Object
 *RECURSION*
              [_] => Array
                (
                  [0] => 1
                  [7] =>
                  [1] => 4
                )

              [tag_start] => 0
              [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
            )

        )

      [parent] =>
      [_] => Array
        (
          [0] => -1
          [1] => 4
        )

      [tag_start] => 0
      [dom:simple_html_dom_node:private] => simple_html_dom Object
 *RECURSION*
    )

  [token_blank:protected] =>

  [token_equal:protected] =>  =/>
  [token_slash:protected] =>  />

  [token_attr:protected] =>  >
  [_charset] => UTF-8
  [_target_charset] => UTF-8
  [default_br_text:protected] =>

  [default_span_text] =>
  [self_closing_tags:protected] => Array
    (
      [img] => 1
      [br] => 1
      [input] => 1
      [meta] => 1
      [link] => 1
      [hr] => 1
      [base] => 1
      [embed] => 1
      [spacer] => 1
    )

  [block_tags:protected] => Array
    (
      [root] => 1
      [body] => 1
      [form] => 1
      [div] => 1
      [span] => 1
      [table] => 1
    )

  [optional_closing_tags:protected] => Array
    (
      [tr] => Array
        (
          [tr] => 1
          [td] => 1
          [th] => 1
        )

      [th] => Array
        (
          [th] => 1
        )

      [td] => Array
        (
          [td] => 1
        )

      [li] => Array
        (
          [li] => 1
        )

      [dt] => Array
        (
          [dt] => 1
          [dd] => 1
        )

      [dd] => Array
        (
          [dd] => 1
          [dt] => 1
        )

      [dl] => Array
        (
          [dd] => 1
          [dt] => 1
        )

      [p] => Array
        (
          [p] => 1
        )

      [nobr] => Array
        (
          [nobr] => 1
        )

      [b] => Array
        (
          [b] => 1
        )

      [option] => Array
        (
          [option] => 1
        )

    )

  [doc:protected] => <html><body>Hello!</body></html>
  [noise:protected] => Array ()
)

メモリのクリーンアップ

file_get_html()などでオブジェクトの生成をくり返すと、「Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 16 bytes)」としてメモリが不足することがあります。なおメモリ不足のエラーは、php.iniのmemory_limitを超えた時点で発生します。

そのような場合にはsimple_html_domオブジェクトのclear()を呼び出します。それによりオブジェクトのプロパティが解放され、循環参照によるメモリリークを回避できます。

$html->clear();

HTML要素の検索

find()メソッドで、条件に一致する要素を取得できます。

// すべてのa要素を検索し、要素の配列を取得する
$ret = $html->find( 'a' );

// 1番目に発見されたa要素を取得する。見つからない場合はnullが返される
$ret = $html->find( 'a', 0 );

// ... PHP 5.4以降ならば、次のようにも記述できる
$ret = $html->find( 'a' )[ 0 ];
// id属性のある、すべての要素を取得する
$ret = $html->find( '[id]' );

// id属性のある、すべてのdiv要素を取得する
$ret = $html->find( 'div[id]' );

// id属性がfooである、すべてのdiv要素を取得する
$ret = $html->find( 'div[id=foo]' );
属性の選択に使用できる演算子
フィルタ   説明
[ attribute ]   指定の属性をもつ要素
[ attribute=value ] = 指定された値である 指定の属性をもつ要素
[ attribute!=value ] != 指定された値である 指定の属性をもたない要素
[ attribute^=value ] ^= 指定された値から始まる値である 指定の属性をもつ要素
[ attribute$=value ] $= 指定された値で終わる値である 指定の属性をもつ要素
[ attribute*=value ] *= 指定された値を含む値である 指定の属性をもつ要素

実行例

$html = str_get_html( '<a><b>Hello!</b></a>' );
echo $html->find( 'b', 0 ); // <b>Hello!</b> と出力される

高度な方法

CSSのIDセレクタクラスセレクタの構文でも、要素を取得できます。

// id属性がfooのすべての要素を取得する
$ret = $html->find( '#foo' );

// class属性がfooのすべての要素を取得する
$ret = $html->find( '.foo' );

またカンマで区切ることで、複数の条件を指定できます。

// すべてのa要素とimg要素を取得する
$ret = $html->find( 'a, img' );

// title属性のある、すべてのa要素とimg要素を取得する
$ret = $html->find( 'a[title], img[title]' );

子孫セレクタ

CSSの子孫セレクタの構文でも、要素を取得できます。

// ul要素の中にあるすべてのli要素を取得する
$es = $html->find( 'ul li' );

// ネストされたdiv要素を取得する
$es = $html->find( 'div div div' );

さらに属性の指定とも組み合わせられます。

// class属性がhelloであるtable要素の中にある、すべてのtd要素を取得する
$es = $html->find( 'table.hello td' );

// table要素の中にある、align=centerの属性をもつすべてのtd要素を取得する
$es = $html->find( 'table td[align=center]' );

ネストされたセレクタ

// ul要素の中にあるすべてのli要素を取得する
foreach( $html->find( 'ul' ) as $ul )
{
  foreach( $ul->find( 'li' ) as $li )
  {
    // $li に結果が格納される
  }
}
// 最初のul要素の中の 最初のli要素を取得する
$e = $html->find( 'ul', 0 )->find( 'li', 0 );

// PHP 5.4以降ならば、次のようにも記述できる
$e = $html->find( 'ul' )[ 0 ]->find( 'li' )[ 0 ];

テキストとコメント

// すべてのテキストブロックを取得する
$es = $html->find( 'text' );

// すべてのコメント ( <!-- ... --> ) を取得する
$es = $html->find( 'comment' );

HTML要素へのアクセス

// 要素からタグを除外したテキストの取得
echo $html->plaintext;

// 要素の外側に、他の要素を設定する
$e->outertext = '<div class="wrap">'.$e->outertext.'<div>';

// 空文字を設定し、要素を削除する
$e->outertext = '';

// 要素を後へ追加する
$e->outertext = $e->outertext.'<div>foo<div>';

// 要素を前に挿入する
$e->outertext = '<div>foo<div>'.$e->outertext;

HTML要素の属性

// 属性を取得する (属性が非値ならば、論理値が返される)
$value = $e->href;

// 属性を設定する (属性が非値ならば、論理値を設定する)
$e->href = 'my link';

// 属性を削除し、値をNULLとする
$e->href = null;

// 属性が存在するか?
if( isset( $e->href ) )
{
    echo 'href exist!';
}

非標準のタグによるパースの失敗

$str = <<<EOM
<ul>
    <li>A<wbr>B<wbr>C</li>
</ul>
EOM;

$html = str_get_html( $str );
echo $html->find( 'li', 0 );

これを実行すると、

<li>A<wbr>B<wbr>C</li>

と表示されることを期待しますが、実際は

<li>A<wbr>B<wbr>C</li>  </ul>

となります。この問題は次のように、末尾が「/>」で閉じられていない空要素を複数含む場合に発生するようです。

<li>A<wbr>B<wbr>C</li>     // ×
<li>A<wbr>BC</li>          // ○
<li>A<wbr />B<wbr>C</li>   // ○
<li>A<wbr />B<wbr />C</li> // ○

ただし<br>や<b>などの、HTML標準のタグでは問題となりません。

<li>A<abc>B<abc>C</li> // ×
<li>A<br>B<br>C</li>   // ○

<li>A<c>B<c>C</li>     // ×
<li>A<b>B<b>C</li>     // ○
PHPのマニュアルから検索