DOM是php比較新的xml和html處理類,可以像javascript那樣方便的操作DOM樹,網(wǎng)上更多的是介紹它處理XML的情況,
很久之前都有注意到這個(gè)問題,沒去處理。但最近服務(wù)器頻繁被攻擊,錯(cuò)誤信息太多,于是想到把這玩意兒干掉。
以前總是使用如下的方式加載HTML:
$doc = new DOMDocument(); $doc->loadHTML('<?xml version="1.0" encoding="UTF-8" ?>' . $html);
但對(duì)于不是特別規(guī)范的HTML文檔,總是有太多的警告信息,太煩:
PHP message: PHP Warning: DOMDocument::loadHTML(): htmlParseEntityRef: expecting ';' in Entity, line: 3 in index.php on line 33
DomDocument 這個(gè)類是調(diào)用 libxml 來解析HTML的,所以在libxml中設(shè)置臨時(shí)不輸出錯(cuò)誤或警告即可:
// modify state $libxml_previous_state = libxml_use_internal_errors(true); // parse $dom->loadHTML($html); // handle errors libxml_clear_errors(); // restore libxml_use_internal_errors($libxml_previous_state);
另外讀取遠(yuǎn)程文件出現(xiàn)的亂碼問題解決方法:
我要處理的html是使用curl從網(wǎng)頁上讀取過來的,一個(gè)是百度的首頁,gb2312字符集,一個(gè)是有道的首頁,utf8字符集,兩者的html頭部分分別如下:
<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>
可以看出百度的代碼非常不規(guī)范,而有道就好多了,這雖然是題外話,其實(shí)還是有些關(guān)系的,后面會(huì)提到。
以上兩段html代碼,用同樣的方式處理結(jié)果卻不同,比如下面簡(jiǎn)單的處理(輸出網(wǎng)頁的title):
$dom = new DOMDocument(); @$dom->loadHTML($html); echo $dom->getElementsByTagName('title')->item(0)->nodeValue; ... $html = $dom->saveHTML();
有道的輸出結(jié)果是正常的,百度卻是亂碼:
????o|??????????? ?°±??¥é??
由于php文檔的loadHTML上說了,DOM內(nèi)部處理全部都是utf8的,所以除了傳入內(nèi)容要utf8化之外,傳入的內(nèi)容中最好還有聲明字符集的html代碼:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
注意,這就是DOM處理html和xml最大的不同了,xml一般要求在第一行就顯示的聲明字符集,而html則靈活得多,可聲明可不聲明。不過不管輸出的內(nèi)容是正常還是亂碼,dom內(nèi)的nodeValue和最終的輸出結(jié)果都是一致的,說明dom工作正常,問題就在輸入數(shù)據(jù)上。
于是,針對(duì)百度的gb2312網(wǎng)頁內(nèi)容,增加了兩項(xiàng)處理,第一項(xiàng)是使用mb_convert_encoding把網(wǎng)頁內(nèi)容由gb2312編碼轉(zhuǎn)換為utf8編碼,第二項(xiàng)是把html中的:
<meta http-equiv=Content-Type content="text/html;charset=gb2312">
替換成了utf8的:
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
這樣按說應(yīng)該是可以了,但百度的處理結(jié)果仍然是亂碼,百思而不得其解,偶然當(dāng)中發(fā)現(xiàn)如果$html的值是這樣的話輸出是正常的:
$html = mb_convert_encoding('<title>測(cè)試test</title>', 'gb2312', 'utf-8'); $html = '<meta http-equiv="Content-Type" content="text/html;charset=gb2312">' . $this->html;
這說明,DOM正確識(shí)別了html代碼中的Content-Type描述,即使html是gb2312編碼的,DOM也能夠自動(dòng)轉(zhuǎn)換為正確的代碼。
現(xiàn)在的情況是這樣的:
DOM工作正常
html已經(jīng)轉(zhuǎn)換為utf8編碼
Content-Type描述也已經(jīng)調(diào)整
怎么還是會(huì)出問題呢?先看看下面的Content-Type描述代碼:
$meta = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
看清楚嘍,如果用$meta直接替換百度html代碼中的那句meta,不會(huì)生效,仍然亂碼;可如果把$meta添加到整個(gè)html代碼前面,也就是<html>前面,輸出就正常了,神奇吧。
于是我就推測(cè),之前百度代碼處理亂碼的原因,可能是在它的html代碼中,meta前面有個(gè)含有中文的<title>,DOM在解析到<title>的時(shí)候,遇到了非ascii字符,而這時(shí)沒有解析到<meta>,DOM不知道整個(gè)html代碼是什么字符集,也就無法正確判斷<title>的編碼,于是糊里糊涂的進(jìn)行了錯(cuò)誤的字符集轉(zhuǎn)換。
為了證實(shí)我的猜測(cè),試著這樣處理一下:只修改<meta>,把定義位置放在<title>前面,把缺少的引號(hào)加上,但是字符集聲明仍然為gb2312,html代碼也不進(jìn)行iconv轉(zhuǎn)換,就像下面這樣(注意為gb2312編碼):
<html> <head> <meta http-equiv="Content-Type" content="text/html;charset=gb2312"> <title>百度一下,你就知道 </title>
執(zhí)行,輸出正常,而且是正常的gb2312編碼,沒有亂碼。所以我的猜測(cè)是正確的,關(guān)于Content-Type的meta聲明一定要放在<title>前面才行。另外上例中如果把nodeValue輸出,是utf8編碼的,也就是DOM的內(nèi)部使用編碼,說明DOM輸入和輸出的時(shí)候都會(huì)進(jìn)行字符集轉(zhuǎn)換(根據(jù)html代碼中的字符集聲明)。
最后,總結(jié)一下,curl讀過來的網(wǎng)頁數(shù)據(jù),全部iconv為utf8編碼,然后把聲明Content-Type的<meta>替換到緊跟在<head>的位置上,再用DOM處理就不會(huì)出現(xiàn)亂碼了。
如對(duì)本文有疑問,請(qǐng)?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會(huì)為你解答??! 點(diǎn)擊進(jìn)入論壇