PHP_DOM處理中文亂碼問題,DOMDocument->loadHTML()處理中文的一點問題

DOM是php比較新的xml和html處理類,能夠像javascript那樣方便的操做DOM樹,網上更多的是介紹它處理XML的狀況,今天我來介紹一個用它處理html時的中文問題,php版本爲5.1.6,全部php代碼均爲utf8編碼。javascript

我要處理的html是使用curl從網頁上讀取過來的,一個是百度的首頁,gb2312字符集,一個是有道的首頁,utf8字符集,二者的html頭部分分別以下:php

<html><head><title>百度一下,你就知道   </title><meta http-equiv=Content-Type content="text/html;charset=gb2312">

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="/pack23501M/index.css" type="text/css"/>
<script type="text/javascript" src="/pack23501M/all.js"></script>
<title>有道</title>

能夠看出百度的代碼很是不規範,而有道就好多了,這雖然是題外話,其實仍是有些關係的,後面會提到。css

以上兩段html代碼,用一樣的方式處理結果卻不一樣,好比下面簡單的處理(輸出網頁的title):html

$dom = new DOMDocument();
@$dom->loadHTML($html);
echo $dom->getElementsByTagName('title')->item(0)->nodeValue;
...
$html = $dom->saveHTML();

有道的輸出結果是正常的,百度倒是亂碼:java

ç™¾åº¦ä¸€ä¸‹ï¼Œä½ å°±çŸ¥é“

因爲php文檔的loadHTML上說了,DOM內部處理所有都是utf8的,因此除了傳入內容要utf8化以外,傳入的內容中最好還有聲明字符集的html代碼:node

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

注意,這就是DOM處理html和xml最大的不一樣了,xml通常要求在第一行就顯示的聲明字符集,而html則靈活得多,可聲明可不聲明。不過無論輸出的內容是正常仍是亂碼,dom內的nodeValue和最終的輸出結果都是一致的,說明dom工做正常,問題就在輸入數據上。web

因而,針對百度的gb2312網頁內容,增長了兩項處理,第一項是使用mb_convert_encoding把網頁內容由gb2312編碼轉換爲utf8編碼,第二項是把html中的:dom

<meta http-equiv=Content-Type content="text/html;charset=gb2312">

替換成了utf8的:curl

<meta http-equiv="Content-Type" content="text/html;charset=utf-8">

這樣按說應該是能夠了,但百度的處理結果仍然是亂碼,百思而不得其解,偶然當中發現若是$html的值是這樣的話輸出是正常的:測試

$html = mb_convert_encoding('<title>測試test</title>', 'gb2312', 'utf-8');
$html = '<meta http-equiv="Content-Type" content="text/html;charset=gb2312">' . $this->html;

這說明,DOM正確識別了html代碼中的Content-Type描述,即便html是gb2312編碼的,DOM也能夠自動轉換爲正確的代碼。

如今的狀況是這樣的:

  • DOM工做正常
  • html已經轉換爲utf8編碼
  • Content-Type描述也已經調整

怎麼仍是會出問題呢?先看看下面的Content-Type描述代碼:

$meta = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';

看清楚嘍,若是用$meta直接替換百度html代碼中的那句meta,不會生效,仍然亂碼;可若是把$meta添加到整個html代碼前面,也就是<html>前面,輸出就正常了,神奇吧。

因而我就推測,以前百度代碼處理亂碼的緣由,多是在它的html代碼中,meta前面有個含有中文的<title>,DOM在解析到<title>的時候,遇到了非ascii字符,而這時沒有解析到<meta>,DOM不知道整個html代碼是什麼字符集,也就沒法正確判斷<title>的編碼,因而糊里糊塗的進行了錯誤的字符集轉換。

爲了證明個人猜想,試着這樣處理一下:只修改<meta>,把定義位置放在<title>前面,把缺乏的引號加上,可是字符集聲明仍然爲gb2312,html代碼也不進行iconv轉換,就像下面這樣(注意爲gb2312編碼):

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=gb2312">
<title>百度一下,你就知道   </title>

執行,輸出正常,並且是正常的gb2312編碼,沒有亂碼。因此個人猜想是正確的,關於Content-Type的meta聲明必定要放在<title>前面才行。另外上例中若是把nodeValue輸出,是utf8編碼的,也就是DOM的內部使用編碼,說明DOM輸入和輸出的時候都會進行字符集轉換(根據html代碼中的字符集聲明)。

最後,總結一下,curl讀過來的網頁數據,所有iconv爲utf8編碼,而後把聲明Content-Type的<meta>替換到緊跟在<head>的位置上,再用DOM處理就不會出現亂碼了。

相關文章
相關標籤/搜索