笔者最近的项目中用到了HtmlAgilityPack,让它担当网络爬虫的html dom parser解析模块。在使用中发现它对中文处理并不好,在下载gb2312编码的网页时多半会出现乱码,搜索网络上的解决方案大多不太成熟。本文中为各位介绍 .Net平台 下c# httpwebrequest httpwebresponse 应用中出现 中文 乱码的通用解决方法。
乱码
中文 乱码 出现的原因
正常情况下,网络服务器会发出正确的Content-Encoding HTTP标头。似乎80%以上的网站都会这么做,浏览器或网络爬虫读取到HTTP 标头后,设置正确的中文 编码,然后显示给用户。但剩下的20%呢?如果你自己开发一个网络爬虫,中文 乱码 绝对会成为一个噩梦,解决的方式有很多种。
c# HttpWebResponse GetResponseStream() 处理
不管怎么说,处理网络流是第一步,如果确定了URI指向的MIME Type是html文本,有三种方式获取文本的编码格式。
HTTP Header Charset 或 Content-Encoding
在使用StreamReader读取流时,使用detectEncodingFromByteOrderMarks自动检测
读取
前两种方式有它的局限性,第一种方式的缺陷很明显,即很多网络服务器并不发出Content-Encoding或Charset HTTP标头,这也是大多数时候出现 中文 乱码 的重要因素。
第二种方式的局限性在于detectEncodingFromByteOrderMarks只能自动识别 UTF-8、Little-Endian Unicode 和 Big-Endian Unicode 文本,对GB2312编码的html文本则无能为力了。
针对上面两种方式的缺陷,最后只能检测 meta元素中的Content-Type了。下面是它C#实现方式:
///
/// Check Html Page Encoding
/// site: http://www.cnphp.info/howto-solve-encoding-problem-in-csharp.html
/// author: freemouse
///
/// NetWork Stream
/// Ecnoding in
/// return read content from stream s
private string CheckEncoding(Stream s,ref Encoding enc)
{
string pattern = "[-\\w]+)";
Regex charSetPattern = new Regex(pattern, RegexOptions.IgnoreCase);
StringBuilder strBuilder = new StringBuilder();
StringBuilder retBuilder = new StringBuilder();
string line = "";
while ( ReadLine(s,strBuilder) )
{
line = strBuilder.ToString();
if (line.Trim().StartsWith("
strBuilder.Remove(0, strBuilder.Length);
retBuilder.AppendLine(line);
Match m = charSetPattern.Match(line);
if (m.Success)
{
string strEnc = m.Groups["charset"].Value;
try
{
enc = Encoding.GetEncoding(strEnc);
break;
}
catch (Exception)
{
//throw new Exception(err.Message);
return retBuilder.ToString();
}
}
}
if (enc != null)
{
return enc.GetString(Encoding.GetEncoding("ISO-8859-1").GetBytes(retBuilder.ToString()));
}
return retBuilder.ToString();
}
///
/// Read a line from NetwrokStream
/// site: http://www.cnphp.info/howto-solve-encoding-problem-in-csharp.html
/// author: freemouse
///
/// Stream to Read
/// Line storage
/// if is end of Stream return false
private bool ReadLine(Stream s,StringBuilder strBuilder)
{
int iChar = -1;
do
{
if (iChar != -1)
{
if (iChar <= 65 && iChar >= 90) {
strBuilder.Append(iChar+32); }
else
strBuilder.Append((char)iChar);
}
} while ((iChar = s.ReadByte()) != (int)'\n' && iChar != -1);
if ( iChar != -1) return true;
return false;
}