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也能夠自動轉換爲正確的代碼。
如今的狀況是這樣的:
怎麼仍是會出問題呢?先看看下面的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處理就不會出現亂碼了。