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> // ○