日期:2014-05-16  浏览次数:20339 次

node.js jsdom gb系列网页中文乱码问题解决方案
Email:longsu2010 at yeah dot net
最近使用node.js写点东西,使用到了jsdom。使用过程中遇到解析GBK或者GB2312编码网页乱码的问题。下面以"http://www.w3school.com.cn"(网页编码gb2312)为例讲解乱码问题及我的应对方案。
如下代码是获取id为w3的dom节点并打印该节点的innerHTML属性,由于其中含有中文,所以会输出许多个问号。
var jsdom = require("jsdom");
var fs = require("fs");
var jquery = fs.readFileSync(__dirname + "/lib/jquery.js").toString();

jsdom.env({
 	html : "http://www.w3school.com.cn/",
  	src : [jquery],
 	done : function (errors, window) {
	    var $ = window.$;
	    console.log( $("#w3")[0].innerHTML );
 	}
});

我想jsdom的env必定会有一个配置项可以设置需要使用什么编码,简单查了一下文档没发现这方面的信息,绝招是看源码(开源就是好)。在源码中发现jsdom使用了request模块,而request可以配置一个encoding配置项来指定编码。request有一这样一个特性,若encoding设置为null,那么request返回的是一个Buffer(着点很重要)。看看jsdom中是怎么写的吧,如下:
request({
	uri : url,
	encoding : config.encoding || 'utf8',
	headers : config.headers || {},
	proxy : config.proxy || null
}, function(err, request, body) {
	processHTML(err, body);
});
看了这段代码我就明白了,在调用jsdom的env方法的时候你给encoding传null没用,他会使用utf8。所以我把这段代码改成了如下这样:
request({
    uri : url,
    encoding : (typeof(config.encoding) === "undefined") ? 'utf8' : config.encoding,
    headers : config.headers || {},
    proxy : config.proxy || null
}, function(err, request, body) {
	processHTML(err, body);
});
这样jsdom使用request模块就可以得到一个Buffer。这还没完,因为jsdom不会给request传递encoding为null的配置项,所以jsdom不会接受到Buffer,因此jsdom中很可能没处理返回值是Buffer的情况。根据上述代码片段可知request的回调函数是processHTML,那就是说返回值处理在processHTML中,代码如下:
processHTML = function(err, html) {

    html += '';
    if(err) {
      return callback(err);
    }
    // 此处省略其他代码
}
看了代码我知道我想错了,人家还真处理了,很通用的一句代码html += '',如果html是Buffer那么这句代码相当于html = html.toString(),而Buffer的toString方法作用正是取Buffer中字符串的,参数为编码方式,默认是utf8。这就不成了,网页是gb2312的。于是我做了如下修改:
 processHTML = function(err, html) {
	if(err) {
		return callback(err);
	}
	if(html instanceof Buffer){
		html = iconvLite.decode(html, config.decoding)
	}
	html += '';
	// 此处省略其他代码
}
如上用到了iconv-lite模块,需要到jsdom中引入。decoding参数是后加的,意义为解码的方式。所以jsdom的env方法调用方式如下即刻比买你中文乱码。
jsdom.env({
 	html : "http://www.w3school.com.cn/",
  	src : [jquery],
  	encoding : null,
	decoding : "GBK",
 	done : function (errors, window) {
	    var $ = window.$;
	    console.log( $("#w3")[0].innerHTML );
 	}
});

到这里就结束了,有两个地方需要说明。
1、回想一下Buffer的toString方法参数默认是utf8,那我们传GBK不就成了么?其实还真不成,详情请阅读node.js关于Buffer的文档。
2、GBK兼容GB2312,所以可以使用GBK搞定GB2312。