DOMDocument::loadHTML が meta の charset を解釈してくれない問題と対策


概要

HTMLソースに「<meta charset="…" />」が指定してあっても DOMDocument::loadHTML は文字コード解釈に失敗することがあるよ、というお話。

実験対象

Qiita のトップページ。「<meta charset="UTF-8">」という記述があり、DOMDocument はこれを解釈できそうに見えます。

qiita.html
<!DOCTYPE html>
<html xmlns:og="http://ogp.me/ns#">
    <head><script type="text/javascript">var NREUMQ=NREUMQ||[];NREUMQ.push(["mark","firstbyte",new Date().getTime()]);</script>
    <meta charset="UTF-8">
    <title>Qiita [キータ] - プログラマの技術情報共有サービス</title>

サンプルコード

dom_charset.php
<?php
$url = "http://qiita.com/";
$body = file_get_contents($url);
$dom = new DOMDocument();
@$dom->loadHTML($body);
$title = $dom->getElementsByTagName('title')->item(0)->textContent;
print "$title\n";

サンプル実行結果

$ php dom_charset.php 

      Qiita [ã­ã¼ã¿] - ãã­ã°ã©ãã®æè¡æå ±å±æãµã¼ãã¹

文字化けだ!/(^o^)\

原因・対策

原因

環境によるかもしれないが、少なくとも自分の環境 (PHP 5.3.3) では、
どうやら DOMDocument::loadHTML は「<meta charset=…」ではなく「<meta http-equiv=…」のタグ情報を元に文字コードを判定しているらしい。

<!-- これがあっても意味がない -->
<meta charset="UTF-8" />

<!-- こっちを解釈する -->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

コード修正

全サイトの管理者に「<meta http-equiv=…」のタグを埋め込んでもらうことを期待するのは現実的ではないので、
取得したHTMLソースを置換してやると良い。(今回はあくまでも対症療法として単純な置換で済ませます)

dom_charset2.php
<?php
$url = "http://qiita.com/";
$body = file_get_contents($url);
$body = str_replace('<meta charset="UTF-8">', '<meta charset="UTF-8"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">', $body);
$dom = new DOMDocument();
@$dom->loadHTML($body);
$title = $dom->getElementsByTagName('title')->item(0)->textContent;
print "$title\n";

実行結果

$ php dom_charset2.php 

      Qiita [キータ] - プログラマの技術情報共有サービス

直った!\(^o^)/