这两天看到几篇关于WebSpider的文章。其中关于抓取网页出现的编码格式问题大家都比较感兴趣,以前在参与帮看网的开发时也遇到过。不过那时候忙于ITDB的BBS开发,没有时间去研究。今天看到解决网爬工具爬取页面信息出现乱码的问题 ,刚好最近离职赋闲在家。所以又挑起了我研究学习的兴趣。现在把我的“研究成果”和大家探讨下:
下面我按照我解决问题的思路来行文
1,要根本解决编码问题,先要从编码的理论入手。
2,计算机是一门实践的科学,多动手尝试吧。
一,和编码相关的理论知识:
中文编码处理(1) -- 编码与字符集,我摘录几句:
如果我们读不同编码的文件 到程序内部处理再保存程另一个文件 涉及到三次编码问题
1 读入文件使用什么编码
2 程序中使用什么编码
3 写出文件使用什么编码
看到这里。可以知道如果自以为先用某种格式把数据从流中读取出来,然后判断,再转换的方式处理编码问题,那么方法本身就错了。结果自然就是不可预期的。当然上面的话并不代表权威。仅仅做为一种分析的参考。
二,http协议和html的规范关于如何得到一个页面的字符编码三种方法:
1.An HTTP "charset" parameter in a "Content-Type" field.
example:
Content-Type: text/html; charset=EUC-JP
2.A META declaration with "http-equiv" set to "Content-Type" and a value set for "charset".
example:
<META http-equiv="Content-Type" content="text/html; charset=EUC-JP">
3.The charset attribute set on an element that designates an external resource.
example:
<A href="http://www.w3.org/" charset="ISO-8859-1">W3C Web site</A>
现在先贴一段常见的抓取网页的代码,方便后续的讨论:
WebResponse webResponse = webRequest.GetResponse();
Stream stream = webResponse.GetResponseStream();
StreamReader sr = new StreamReader(stream, Encoding.Default);
string html = sr.ReadToEnd();
return html;
常见的识别编码格式都是要么从HttpWebResponse的ContentEncoding和CharacterSet去分析,要么从提取的网页里的分析(二列出的三种方法),现在的问题就出在既然HttpWebResponse的ContentEncoding和CharacterSet并不可靠。而要从流读数据必须指定编码,但现在并不能可靠的确定数据源的正确编码,而尝试用一种编码格式读然后转又会遭遇上叙一所说的问题。这让我想起了我以前写过的“由一道面试题引起的疑问与思考”里关于XML编码格式问题,里面谈到BOM(字节顺序标记)的问题,转其中的几句话:
W3C定义了三条XML解析器如何正确读取XML文件的编码的规则:
1,如果文挡有BOM(字节顺序标记,一般来说,如果保存为unicode格式,则包含BOM,ANSI则无),就定义了文件编码
2,如果没有BOM,就查看XML声明的编码属性
3,如果上述两个都没有,就假定XML文挡采用UTF-8编码
其实网页也是一种文本格式的东西,其规则也应该类似,我搜索了下,找到更详细的资料:
1,如果流中是以0xef, 0xbb, 0xbf开头的话,可以确定编码格式utf-8的
2,如果流中是以0xff,0xfe开头的话,可以确定编码格式是utf-16的
如果仅仅按照上面所列两种情况去判断的,还显然不够严谨,但是到目前为止,我还没找到更详细的关于各种编码的BOM的更多资料。
写到这里,我不得不告诉你,上面的一切探索对于.net来说都是徒劳的,因为.net已经内置了这样的判断方法:
StreamReader sr = new StreamReader(stream, Encoding.Default,true);
就多加一个true,ms帮你完成BOM的检测。具体的你可以看MSDN的帮助文挡。
我在开篇说到计算机是一门实践的科学,我测试了几个网页都没发现乱码问题。当然这并不表示就完全没有问题,只是一时没找到让它乱码的网页,如果你发现了,请你一定要告诉我。我们一起来研究下。
最后,我想推翻我刚才的结论:上面的一切探索对于.net来说都是徒劳的;因为我看到下面的代码的时候,我知道why,而不仅仅是how !
Reflector出来的StreamReader关于通过BOM检测编码格式的代码:
private void DetectEncoding()
{
if (this.byteLen >= 2)
{
this._detectEncoding = false;
bool flag1 = false;
if ((this.byteBuffer[0] == 0xfe) && (this.byteBuffer[1] == 0xff))
{
this.encoding = new UnicodeEncoding(true, true);
this.CompressBuffer(2);
flag1 = true;
}
else if ((this.byteBuffer[0] == 0xff) && (this.byteBuffer[1] == 0xfe))
{
if (((this.byteLen >= 4) && (this.byteBuffer[2] == 0)) && (this.byteBuffer[3] == 0))
{
this.encoding = new UTF32Encoding(false, true);
this.CompressBuffer(4);
}
else
{
this.encoding = new UnicodeEncoding(false, true);
this.CompressBuffer(2);
}
flag1 = true;
}
else if (((this.byteLen >= 3) && (this.byteBuffer[0] == 0xef)) && ((this.byteBuffer[1] == 0xbb) && (this.byteBuffer[2] == 0xbf)))
{
this.encoding = Encoding.UTF8;
this.CompressBuffer(3);
flag1 = true;
}
else if ((((this.byteLen >= 4) && (this.byteBuffer[0] == 0)) && ((this.byteBuffer[1] == 0) && (this.byteBuffer[2] == 0xfe))) && (this.byteBuffer[3] == 0xff))
{
this.encoding = new UTF32Encoding(true, true);
flag1 = true;
}
else if (this.byteLen == 2)
{
this._detectEncoding = true;
}
if (flag1)
{
this.decoder = this.encoding.GetDecoder();
this._maxCharsPerBuffer = this.encoding.GetMaxCharCount(this.byteBuffer.Length);
this.charBuffer = new char[this._maxCharsPerBuffer];
}
}
}
水平有限,不妥之处,欢迎指正。