PHPでHTMLのパースって、実はいまいち使い勝手のいいものがないんですよね。
DOMはいったんDOMElementにしてしまうと元のHTMLに戻すのが何故か大変、SimpleXMLはアバウトなHTMLを読み込んでくれない、XMLパーサは使い方がやたらややこしい、と一長一短というか一短一短です。
そもそも何れもXML用であってHTML用ではないので当たり前かもしれませんが。
HTMLの誤りを修正するためのツールとしてTidyというものが存在しますが、どういうわけかPHPから利用することが可能です。
で、これがHTMLのパーサとしてもわりと優秀です。
さすがにDOMDocument::getElementById()ほどの便利メソッドはありませんが、比較的簡単にHTMLを掘っていくことが可能です。
とりあえず作成。
<?php
//HTMLを取得
$html = file_get_contents('http://yuubiseiharukana.blog.shinobi.jp/Entry/501/');
//Tidy
$tidyConfig = array('indent' => true, 'output-xhtml' => true, 'wrap' => 200);
$tidy = new tidy();
$tidy->parseString($html, $tidyConfig, 'utf8');
//<body>を取得
$tidyBody = $tidy->body();
//本文を取得
//CSS上のパス:html body div#whole div#contents div#main div div.entry_table div.entry_text
$tidyText = $tidyBody->child[1]->child[4]->child[1]->child[1]->child[0]->child[1];
print($tidyText);
Tidyのコンフィグはここらへんに一覧がありますが、いまいちなんなのかよくわかりません。よくわからないのでサンプルのまま使用しています。
あとはTidyオブジェクトに対してHTMLタグの入れ子の順番を指定するだけで、非常に簡単に本文を取得できます。
とはいえ、例では番号を直接指定しているのですが実はあまりよくない方法です。
何故ってchild[1]やchild[4]は、単にノード内の要素を上から順に数えた値なので、タグをひとつ追加されるだけでずれてしまうのです。
本当はXPathやCSSパスで指定するのが安全なのですが、残念ながらそのような機能はないようです。
ということで子ノードを順に取得してひとつひとつ確認していくしかありませんが、ところが何故かTidyはforeachやcount()が使えません。
従ってたとえば子ノードのうち特定のタグを取得したい場合は以下のように書く必要があります。
<?php
//Countable、Traversable
count($tidyBody); //必ず1になる
foreach($tidyBody as $key=>$val){
//$tidyBody[0]のプロパティのループになってしまう
}
//<div id="whole">を取得
$loop = 0;
while(1){
$tmp = $tidyBody->child[$loop];
$loop++;
if(!$tmp){break;}
if($tmp->id !== TIDY_TAG_DIV){continue;}
if($tmp->attribute['id'] !== 'whole'){continue;}
$tidyWhole = $tmp;
break;
}
//<div id="contents">を拾う
$loop = 0;
while(1){
$tmp = $tidyWhole->child[$loop];
$loop++;
if(!$tmp){break;}
if($tmp->id !== TIDY_TAG_DIV){continue;}
if($tmp->attribute['id'] !== 'contents'){continue;}
$tidyContents = $tmp;
break;
}
ちょっとどうなんだって気がする。あとUndefined propertyのNoticeが出るのも微妙。
というかTidyでXHTML化してSimpleXMLに突っ込んだ方が早くない?という気がしてきた。
PR
トラックバック
トラックバックURL: