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();
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' );
// 要素からタグを除外したテキストの取得 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;
// 属性を取得する (属性が非値ならば、論理値が返される) $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> // ○