トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

PHP5、未整形HTMLをSimpleXMLへ変換

[カテゴリ:言語]
[カテゴリ:PHP]
[カテゴリ:PHP5]

PHP5、未整形HTMLをSimpleXMLへ変換

始めに

PHP5でのスクレイピングについて調査してみた。
正規表現でやってもいいのだけど。

PHP5でやるのだからSimpleXMLでやってみたい。
となると、いかに未整形HTMLを整形済にしてSimpleXMLとするかが
課題となる。これについて調べてみた。

↓これが答え。なるものを見つけました。
HTMLParser(PEARのXML_HTMLSax3使用)orTidyで整形してるそうです。

HTMLをXML化してDOMやXPathで操作するWebスクレイピング用PHPクラス : Under Construction, Baby:
# SimpleXML+HTMLParser or Tidy+HTTP_Request+Cache_Lite
http://www.rcdtokyo.com/ucb/contents/i000851.php

これで調査終了。というのも寂しいので。
もう少しお手軽にできる方法がないものか探してみた。

↓ここによると DOMDocument->loadHTML() を使えば、
未整形HTMLをDOM化できるとのこと。

KoshigoeBLOG: PHPのDOMDocumentの文字化けなど:
# DOMDocument->loadHTML
http://blog.koshigoe.jp/archives/2007/04/phpdomdocument.html

↓さらにDOMをSimpleXMLへ変換できるらしい。

SimpleXMLとXMLReaderとDomを組み合わせてPHPで巨大なXMLデータを高速に処理する方法 - ぎじゅっやさん:
# DOMDocument を SimpleXML へ変換
http://hain.jp/index.php/tech-j/2007/06/25/PHP%E3%81%A7XML

これらを総合すると。

未整形HTML
   ↓
DOMDocument
   ↓
SimpleXML

とできるのでは?
ということで実験してみる。

サンプルスクリプト

html_to_simplexml() が 未整形HTMLをSimpleXMLへ変換する関数です。
細かいことはコメント参照。

「おせっかい?な処理」なるものがありますが、
サンプルとして使った未整形HTMLから、できるかぎり期待してる
状態へ変換できるようつじつま合わせとしてやってる処理なので
恒久的に有るほうが良いのかはなぞ。

<?

/**
 * 未整形HTMLをSimpleXMLへ変換
 *
 * @param string $html_ 未整形HTML
 * @param bool   $flg_  おせっかい?な処理を行うか。
 * @return SimpleXMLElement
 */
function html_to_simplexml($html_, $flg_ = true)
{
	// HTMLはUTF-8に変換しておくこと。
	// metaタグ中のcharsetはUTF-8にしておくこと。
	
	// 未整形HTMLをDOMに読込む
	// 読むときにWarningが出ることがあるので@で抑制する。
	$dom = new DOMDocument();
	@$dom->loadHTML($html_);
	
	// DOMをSimpleXMLへ変換
	$ret = simplexml_import_dom($dom);

	// ここまででもいいのだけど。
	// XML宣言が付いていないので付与する。
	if ($flg_ === true) {
		$str = $ret->asXML();
		{
			// XML宣言付与
			if (1 !== preg_match('/^<\\?xml version="1.0"/', $str)) {
				$str = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $str;
			}
			
			// これもなくても良いかもしれないけど、
			// HTML中の改行が数値文字参照になってしまったので、
			// 文字に戻す。
			$str = numentToChar($str);
		}
		$ret = simplexml_load_string($str);
	}
	
	return $ret;
}

/**
 * 数値文字参照を文字に戻す。
 *
 * 以下より
 * http://blog.koshigoe.jp/archives/2007/04/phpdomdocument.html
 * 
 * @param string $string
 * @return string
 */
function numentToChar($string)
{
    $excluded_hex = $string;
    if (preg_match("/&#[xX][0-9a-zA-Z]{2,8};/", $string)) {
        // 16 進数表現は 10 進数に変換
        $excluded_hex = preg_replace("/&#[xX]([0-9a-zA-Z]{2,8});/e",
                                     "'&#'.hexdec('$1').';'", $string);
    }
    return mb_decode_numericentity($excluded_hex,
                                   array(0x0, 0x10000, 0, 0xfffff),
                                   "UTF-8");
}

function test($html_, $flg_)
{
	$simplexml = html_to_simplexml($html_, $flg_);
	
	echo '↓↓↓変換元 <br />';
	$str = print_r($html_, true);
	echo '<pre>';
	echo htmlentities($str, ENT_QUOTES, mb_internal_encoding());
	echo '</pre>';
	
	echo '↓↓↓変換後 <br />';
	$str = print_r($simplexml, true);
	echo '<pre>';
	echo htmlentities($str, ENT_QUOTES, mb_internal_encoding());
	echo '</pre>';
		
	echo '↓↓↓再構築 <br />';
	$str = $simplexml->asXML();
	echo '<pre>';
	echo htmlentities($str, ENT_QUOTES, mb_internal_encoding());
	echo '</pre>';
}

$html = '
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>タイトル</title>
</head>
<body>
<a name="anc1"></a>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc1の1つめ</p>
  </div>
</div>
<a name="anc2"></a>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc2の1つめ</p>
  </div>
</div>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc2の2つめ</p>
  </div>
</div>
</body>
';
echo '<hr />';
echo 'おせっかい処理あり<br />';
test($html, true);
echo '<hr />';
echo 'おせっかい処理なし<br />';
test($html, false);
exit;

$html = '
<TITLE>BOGUS HTML EXAMPLE</TITLE>
<B><FONT COLOR=#FF0000>This is a bogus HTML example for test_feed.php.</b></font>
<UL>
<LI><A HREF=test_feed.html#1>This is item #1 (2007/4/1)
<LI><A HREF=test_feed.html#2>This is item #2 (2007/4/2)
<LI><A HREF=test_feed.html#3>This is item #3 (2007/4/5)
<LI><A HREF=test_feed.html#4>This is item #4 (2007/4/7)
';
test($html);

?>

サンプルスクリプト実行結果

--------------------------------------------------------------------------------
おせっかい処理あり
↓↓↓変換元 

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>ウiquest;ウcurren;トル</title>
</head>
<body>
<a name="anc1"></a>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc1てreg;1つめ</p>
  </div>
</div>
<a name="anc2"></a>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc2てreg;1つめ</p>
  </div>
</div>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc2てreg;2つめ</p>
  </div>
</div>
</body>

↓↓↓変換後 

SimpleXMLElement Object
(
    [head] => SimpleXMLElement Object
        (
            [meta] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [http-equiv] => content-type
                            [content] => text/html; charset=UTF-8
                        )

                )

            [title] => ウiquest;ウcurren;トル
        )

    [body] => SimpleXMLElement Object
        (
            [a] => Array
                (
                    [0] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [name] => anc1
                                )

                        )

                    [1] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [name] => anc2
                                )

                        )

                )

            [div] => Array
                (
                    [0] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [class] => div_1
                                )

                            [div] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [class] => div_div_1
                                        )

                                    [p] => anc1てreg;1つめ
                                )

                        )

                    [1] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [class] => div_1
                                )

                            [div] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [class] => div_div_1
                                        )

                                    [p] => anc2てreg;1つめ
                                )

                        )

                    [2] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [class] => div_1
                                )

                            [div] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [class] => div_div_1
                                        )

                                    [p] => anc2てreg;2つめ
                                )

                        )

                )

        )

)

↓↓↓再構築 

<?xml version="1.0" encoding="UTF-8"?>
<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"/><title>ウiquest;ウcurren;トル</title></head><body>
<a name="anc1"/>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc1てreg;1つめ</p>
  </div>
</div>
<a name="anc2"/>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc2てreg;1つめ</p>
  </div>
</div>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc2てreg;2つめ</p>
  </div>
</div>
</body></html>


--------------------------------------------------------------------------------
おせっかい処理なし
↓↓↓変換元 

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>ウiquest;ウcurren;トル</title>
</head>
<body>
<a name="anc1"></a>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc1てreg;1つめ</p>
  </div>
</div>
<a name="anc2"></a>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc2てreg;1つめ</p>
  </div>
</div>
<div class="div_1">
  <div class="div_div_1">
    <p class="p_1">anc2てreg;2つめ</p>
  </div>
</div>
</body>

↓↓↓変換後 

SimpleXMLElement Object
(
    [head] => SimpleXMLElement Object
        (
            [meta] => SimpleXMLElement Object
                (
                    [@attributes] => Array
                        (
                            [http-equiv] => content-type
                            [content] => text/html; charset=UTF-8
                        )

                )

            [title] => ウiquest;ウcurren;トル
        )

    [body] => SimpleXMLElement Object
        (
            [a] => Array
                (
                    [0] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [name] => anc1
                                )

                        )

                    [1] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [name] => anc2
                                )

                        )

                )

            [div] => Array
                (
                    [0] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [class] => div_1
                                )

                            [div] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [class] => div_div_1
                                        )

                                    [p] => anc1てreg;1つめ
                                )

                        )

                    [1] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [class] => div_1
                                )

                            [div] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [class] => div_div_1
                                        )

                                    [p] => anc2てreg;1つめ
                                )

                        )

                    [2] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [class] => div_1
                                )

                            [div] => SimpleXMLElement Object
                                (
                                    [@attributes] => Array
                                        (
                                            [class] => div_div_1
                                        )

                                    [p] => anc2てreg;2つめ
                                )

                        )

                )

        )

)

↓↓↓再構築 

<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"/><title>ウiquest;ウcurren;トル</title></head><body>&#13;
<a name="anc1"/>&#13;
<div class="div_1">&#13;
  <div class="div_div_1">&#13;
    <p class="p_1">anc1てreg;1つめ</p>&#13;
  </div>&#13;
</div>&#13;
<a name="anc2"/>&#13;
<div class="div_1">&#13;
  <div class="div_div_1">&#13;
    <p class="p_1">anc2てreg;1つめ</p>&#13;
  </div>&#13;
</div>&#13;
<div class="div_1">&#13;
  <div class="div_div_1">&#13;
    <p class="p_1">anc2てreg;2つめ</p>&#13;
  </div>&#13;
</div>&#13;
</body></html>

使い物になるか?

未整形HTMLの未整形具合によってくるので微妙かも。

まぁ、それっぽくは変換されてますが、
以下のような閉じタグ欠如が多いものは
エラーにはならないものの、結果はちょっとおかしいです。
item #2-4 がなくなったのかと思いきや、再構築すれば復活してます。
ですが、Aタグの閉じが変なところにあります。

$html = '
<TITLE>BOGUS HTML EXAMPLE</TITLE>
<B><FONT COLOR=#FF0000>This is a bogus HTML example for test_feed.php.</b></font>
<UL>
<LI><A HREF=test_feed.html#1>This is item #1 (2007/4/1)
<LI><A HREF=test_feed.html#2>This is item #2 (2007/4/2)
<LI><A HREF=test_feed.html#3>This is item #3 (2007/4/5)
<LI><A HREF=test_feed.html#4>This is item #4 (2007/4/7)
';
--------------------------------------------------------------------------------
おせっかい処理あり
↓↓↓変換元 

<TITLE>BOGUS HTML EXAMPLE</TITLE>
<B><FONT COLOR=#FF0000>This is a bogus HTML example for test_feed.php.</b></font>
<UL>
<LI><A HREF=test_feed.html#1>This is item #1 (2007/4/1)
<LI><A HREF=test_feed.html#2>This is item #2 (2007/4/2)
<LI><A HREF=test_feed.html#3>This is item #3 (2007/4/5)
<LI><A HREF=test_feed.html#4>This is item #4 (2007/4/7)

↓↓↓変換後 

SimpleXMLElement Object
(
    [head] => SimpleXMLElement Object
        (
            [title] => BOGUS HTML EXAMPLE
            [b] => SimpleXMLElement Object
                (
                    [font] => This is a bogus HTML example for test_feed.php.
                )

        )

    [body] => SimpleXMLElement Object
        (
            [ul] => SimpleXMLElement Object
                (
                    [li] => SimpleXMLElement Object
                        (
                            [a] => This is item #1 (2007/4/1)

                        )

                )

        )

)

↓↓↓再構築 

<?xml version="1.0" encoding="UTF-8"?>
<html><head><title>BOGUS HTML EXAMPLE</title><b><font color="#FF0000">This is a bogus HTML example for test_feed.php.</font></b></head><body><ul><li><a href="test_feed.html#1">This is item #1 (2007/4/1)
<li><a href="test_feed.html#2">This is item #2 (2007/4/2)
<li><a href="test_feed.html#3">This is item #3 (2007/4/5)
<li><a href="test_feed.html#4">This is item #4 (2007/4/7)
</a></li></a></li></a></li></a></li></ul></body></html>


--------------------------------------------------------------------------------
おせっかい処理なし
↓↓↓変換元 

<TITLE>BOGUS HTML EXAMPLE</TITLE>
<B><FONT COLOR=#FF0000>This is a bogus HTML example for test_feed.php.</b></font>
<UL>
<LI><A HREF=test_feed.html#1>This is item #1 (2007/4/1)
<LI><A HREF=test_feed.html#2>This is item #2 (2007/4/2)
<LI><A HREF=test_feed.html#3>This is item #3 (2007/4/5)
<LI><A HREF=test_feed.html#4>This is item #4 (2007/4/7)

↓↓↓変換後 

SimpleXMLElement Object
(
    [head] => SimpleXMLElement Object
        (
            [title] => BOGUS HTML EXAMPLE
            [b] => SimpleXMLElement Object
                (
                    [font] => This is a bogus HTML example for test_feed.php.
                )

        )

    [body] => SimpleXMLElement Object
        (
            [ul] => SimpleXMLElement Object
                (
                    [li] => SimpleXMLElement Object
                        (
                            [a] => This is item #1 (2007/4/1)

                        )

                )

        )

)

↓↓↓再構築 

<html><head><title>BOGUS HTML EXAMPLE</title><b><font color="#FF0000">This is a bogus HTML example for test_feed.php.</font></b></head><body><ul><li><a href="test_feed.html#1">This is item #1 (2007/4/1)&#13;
<li><a href="test_feed.html#2">This is item #2 (2007/4/2)&#13;
<li><a href="test_feed.html#3">This is item #3 (2007/4/5)&#13;
<li><a href="test_feed.html#4">This is item #4 (2007/4/7)&#13;
</a></li></a></li></a></li></a></li></ul></body></html>

その後(2008/02/12)

参考にして頂いたようなので逆リンク
時間できたらソース拝見させていただこう。

Liner Note - PHP汎用スクレイピングライブラリを作ってみた:
http://note.openvista.jp/251/

最終更新時間:2008年02月12日 16時04分22秒