DOM是php比较新的xml和html处理类,可以像javascript那样方便的操作DOM树,网上更多的是介绍它处理XML的情况,今天我来介绍一个用它处理html时的中文问题,php版本为5.1.6,所有php代码均为utf8编码。
我要处理的html是使用curl从网页上读取过来的,一个是百度的首页,gb2312字符集,一个是有道的首页,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>
可以看出百度的代码非常不规范,而有道就好多了,这虽然是题外话,其实还是有些关系的,后面会提到。
以上两段html代码,用同样的方式处理结果却不同,比如下面简单的处理(输出网页的title):
$dom = new DOMDocument();
@$dom->loadHTML($html);
echo $dom->getElementsByTagName('title')->item(0)->nodeValue;
...
$html = $dom->saveHTML();
有道的输出结果是正常的,百度却是乱码:
ç¾åº¦ä¸ä¸ï¼ä½ å°±ç¥é
由于[php文档的loadHTML](http://us3.php.net/manual/en/function.dom-domdocument-loadhtml.php)上说了,DOM内部处理全部都是utf8的,所以除了传入内容要utf8化之外,传入的内容中最好还有声明字符集的html代码:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
注意,这就是DOM处理html和xml最大的不同了,xml一般要求在第一行就显示的声明字符集,而html则灵活得多,可声明可不声明。不过不管输出的内容是正常还是乱码,dom内的nodeValue和最终的输出结果都是一致的,说明dom工作正常,问题就在输入数据上。
于是,针对百度的gb2312网页内容,增加了两项处理,第一项是使用mb_convert_encoding
把网页内容由gb2312编码转换为utf8编码,第二项是把html中的:
<meta http-equiv=Content-Type content="text/html;charset=gb2312">
替换成了utf8的:
<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处理就不会出现乱码了。
百度不识别UTF8编码,有的时候直接现实乱码.
遇到乱码了,百度到这。。
没想到网页里的meta 也会被dom读取。。。
谢谢lz分享研究成果
会的,也是dom树的组成部分,dom根是
<html>
读取xml文件的时候是乱码该怎么解决呀,xml是utf-8的编码,读出来中文乱码
我觉得可以看看 xml 中有没有关于字符集的 meta 声明,上面已经讲了原因了。
遇到和你一样的问题,找了半天。终于得到解决了。谢谢
但是奇怪的是相当的代码,我在本机上测试是正常的,但是到了服务器上就显示筹码。这和服务器设置有关否?
mb_convert_encoding 写错了 第二个形参是转成什么编码
应该是 $str, “UTF-8”, “GB2312”
和iconv有区别~
感谢楼主分享,解决了我今天的问题。
感谢楼主,你解决了我大问题了。我走了不少弯路,最开始以为是curl采集来的东西是经常gzip压缩的原因,后来发现只要一 loadHtml就出现乱码,而且并不是所有的网站都乱码,只有个别是的是这样。看了你这篇文章茅塞顿开。 近年来我查了一下W3C的HTML5规范,里面明确无误地写着,Charset要紧跟在 后面、所有元素之前出现。
这让我想起来在开妈学做 WordPress主题文件的时候,里面也有这么一条加粗说明,当时没有重视。现在看来,不尊重标准是会出大麻烦的