详解nodejs爬虫程序解决gbk等中文编码问题


Posted in NodeJs onApril 06, 2017

使用nodejs写了一个爬虫的demo,目的是提取网页的title部分。

遇到最大的问题就是网页的编码与nodejs默认编码不一致造成的乱码问题。nodejs支持utf8, ucs2, ascii, binary, base64, hex等编码方式,但是对于汉语言来说编码主要分为三种,utf-8,gb2312,gbk。这里面gbk是完全兼容gb2312的,因此在处理编码的时候主要就分为utf-8以及gbk两大类。(这是在没有考虑到其他国家的编码情况,比如日本的Shift_JIS编码等,同时这里这个iconv-lite模块支持的编码方法有限)。

首先说一下浏览器显示网页内容的时候是如何处理编码问题的。服务器和客户端进行通信,服务端将网页按照指定的编码方式(比如gbk)编码成为二进制码流(即我们使用wireshark抓包看到额16进制码流)传送给我们的客户端。客户端则会根据网页源码中所规定的编码方式,由浏览器调用对应的解码器,将二进制码流解码后显示出来。而编码方式通常在网页中是如下内容表示:

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

或者

<meta charset=utf-8"/>

如果客户端是nodejs爬虫请求程序,由于nodejs默认的编码方式是utf-8,因此爬虫程序将接收到的二进制码流以字符串(默认方式utf-8)显示的时候则会显示乱码。这个时候需要将原始的二进制码流按照网页原来的编码方式解码,则不会出现乱码。

因此解决方法如下:

将接收到的网页源码以二进制的方式存储下来,处理二进制数据流使用Buffer全局对象。

res.on('data', function(data) {

  htmlData.push(data);
  htmlDataLength += data.length;
 });
var bufferHtmlData = Buffer.concat(htmlData,htmlDataLength);

然后对这些二进制的数据调用对应的解码程序。iconv-lite模块用于解码,cheerio模块用于解析网页内容。

decodeHtmlData = iconv.decode(bufferHtmlData,'gbk');

var $ = cheerio.load(decodeHtmlData, {decodeEntities: false});

 $('title','head').each(function(i, e) {

  htmlHeadTitle = $(e).text();
  console.log(htmlHeadTitle);
 });

上述bufferHtmlData为二进制码流,decodeHtmlData为将二进制码流通过gbk编码规则转换为unicode编码对应的数字(即usc2字节流),然后在转换为对应的字符串。下述为iconv-lite源码中解码部分,地址在这里:

fromEncoding: function(buf) {
          buf = ensureBuffer(buf);
          var idx = 0, len = 0,
            newBuf = new Buffer(len*2),unicode,gbkcode;
          for (var i = 0, _len = buf.length; i < _len; i++, len++) {
            if (!!(buf[i] & 0x80)) {//the high bit is 1, so this byte is gbkcode's high byte.skip next byte
              i++;
            }
          }
          var newBuf = new Buffer(len*2);
          for (var i = 0, j = 0, _len = buf.length; i < _len; i++, j++) {
            var temp = buf[i], gbkcode, unicode;
            if (temp & 0x80) {
              gbkcode = (temp << 8) + buf[++i];
              unicode = table[gbkcode] || iconv.defaultCharUnicode.charCodeAt(0);//not found in table, replace with defaultCharUnicode
            }else {
              unicode = temp;
            }
            newBuf[j*2] = unicode & 0xFF;//low byte
            newBuf[j*2+1] = unicode >> 8;//high byte
          }
          return newBuf.toString('ucs2');
        }

可以看到最终返回的是newBuf.toString(‘ucs2')字符串。

爬虫程序源码如下:

var cheerio = require('cheerio');
var http = require('http');
var iconv = require('iconv-lite');
var htmlData = [];
var htmlDataLength = 0;
var count = 0;

http.globalAgent = 'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1';
http.get('http://www.cr173.com', function(res) {

 res.on('data', function(data) {

  htmlData.push(data);
  htmlDataLength += data.length;
  count ++;
 });

 res.on('end',function(){

  callback(htmlData);
 });

});

function callback(htmlData){

 console.log(count);
 var bufferHtmlData = Buffer.concat(htmlData,htmlDataLength);
 var charset = '';
 var decodeHtmlData;
 var htmlHeadTitle = '';
 var htmlHeadCharset = '';
 var htmlHeadContent = '';
 var index = 0;

 var $ = cheerio.load(bufferHtmlData, {decodeEntities: false});

 $('meta','head').each(function(i, e) {

  htmlHeadCharset = $(e).attr('charset');
  htmlHeadContent = $(e).attr('content');

  if(typeof(htmlHeadCharset) != 'undefined'){

   charset = htmlHeadCharset;
  }

  if(typeof(htmlHeadContent) != 'undefined'){

   if(htmlHeadContent.match(/charset=/ig)){

    index = htmlHeadContent.indexOf('=');
    charset = htmlHeadContent.substring(index+1);
   }
  }
 });

 //此处为什么需要对整个网页进行转吗,是因为cheerio这个组件不能够返回buffer,iconv则无法转换之
 if(charset.match(/gb/ig)){

  decodeHtmlData = iconv.decode(bufferHtmlData,'gbk');
 }
 else{//因为有可能返回的网页中不存在charset字段,因此默认都是按照utf8进行处理

  decodeHtmlData = iconv.decode(bufferHtmlData,'utf8');
 }

 var $ = cheerio.load(decodeHtmlData, {decodeEntities: false});

 $('title','head').each(function(i, e) {

  htmlHeadTitle = $(e).text();
  console.log(htmlHeadTitle);
 });

 console.log(charset);

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
nodejs教程之制作一个简单的文章发布系统
Nov 21 NodeJs
实例详解Nodejs 保存 payload 发送过来的文件
Jan 14 NodeJs
详解nodejs 文本操作模块-fs模块(一)
Dec 22 NodeJs
解决nodejs中使用http请求返回值为html时乱码的问题
Feb 18 NodeJs
Nodejs读取文件时相对路径的正确写法(使用fs模块)
Apr 27 NodeJs
ajax +NodeJS 实现图片上传实例
Jun 06 NodeJs
NodeJs form-data格式传输文件的方法
Dec 13 NodeJs
nodejs+mongodb aggregate级联查询操作示例
Mar 17 NodeJs
解决Nodejs全局安装模块后找不到命令的问题
May 15 NodeJs
nodejs 如何手动实现服务器
Aug 20 NodeJs
NodeJs生成sitemap站点地图的方法示例
Jun 11 NodeJs
nodejs实现聊天机器人功能
Sep 19 NodeJs
NodeJS基础API搭建服务器详细过程记录
Apr 01 #NodeJs
Ajax异步文件上传与NodeJS express服务端处理
Apr 01 #NodeJs
3分钟快速搭建nodejs本地服务器方法运行测试html/js
Apr 01 #NodeJs
nodejs使用express创建一个简单web应用
Mar 31 #NodeJs
nodejs实现邮件发送服务实例分享
Mar 29 #NodeJs
NodeJs测试框架Mocha的安装与使用
Mar 28 #NodeJs
NodeJS测试框架mocha入门教程
Mar 28 #NodeJs
You might like
比较全的PHP 会话(session 时间设定)使用入门代码
2008/06/05 PHP
如何使用FireFox插件FirePHP调试PHP
2013/07/23 PHP
ThinkPHP3.1新特性之动态设置自动完成和自动验证示例
2014/06/19 PHP
浅谈php自定义错误日志
2015/02/13 PHP
laravel7学习之无限级分类的最新实现方法
2020/09/30 PHP
返回对象在当前级别中是第几个元素的实现代码
2011/01/20 Javascript
jquery动态加载js三种方法实例
2013/08/03 Javascript
javascript回车完美实现tab切换功能
2014/03/13 Javascript
jQuery瀑布流插件Wookmark使用实例
2014/04/02 Javascript
jQuery插件PageSlide实现左右侧栏导航菜单
2015/04/12 Javascript
jquery分割字符串的方法
2015/06/24 Javascript
JavaScript对象学习小结
2015/09/02 Javascript
JS使用eval解析JSON的注意事项分析
2015/11/14 Javascript
jquery解析XML及获取XML节点名称的实现代码
2016/05/18 Javascript
js判断radiobuttonlist的选中值显示/隐藏其它模块的实现方法
2016/08/25 Javascript
用js实现简单算法的实例代码
2016/09/24 Javascript
js基本算法:冒泡排序,二分查找的简单实例
2016/10/08 Javascript
快速解决js开发下拉框中blur与click冲突
2016/10/10 Javascript
Vue2.0表单校验组件vee-validate的使用详解
2017/05/02 Javascript
在Bootstrap开发框架中使用dataTable直接录入表格行数据的方法
2018/10/25 Javascript
JS实现处理时间,年月日,星期的公共方法示例
2019/05/31 Javascript
[36:29]2018DOTA2亚洲邀请赛 4.1 小组赛 A组加赛 LGD vs TNC
2018/04/02 DOTA
编写自定义的Django模板加载器的简单示例
2015/07/21 Python
python实现多线程端口扫描
2019/08/31 Python
Python实现线性插值和三次样条插值的示例代码
2019/11/13 Python
澳大利亚买卖正宗二手奢侈品交易平台:Luxe.It.Fwd
2019/10/16 全球购物
会计与审计专业大专生求职信
2013/10/03 职场文书
迟到检讨书800字
2014/01/13 职场文书
母亲追悼会答谢词
2014/01/27 职场文书
电气自动化专业职业规划范文
2014/02/16 职场文书
护理专业毕业生自我鉴定总结
2014/03/24 职场文书
《陈涉世家》教学反思
2014/04/12 职场文书
无财产离婚协议书范本
2014/10/28 职场文书
教师个人考察材料
2014/12/16 职场文书
毕业典礼致辞
2015/07/29 职场文书
2016新年问候语大全
2015/11/11 职场文书