详解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实用示例 缩址还原
Dec 28 NodeJs
详谈nodejs异步编程
Dec 04 NodeJs
浅谈NodeJS中require路径问题
May 07 NodeJs
使用NodeJs 开发微信公众号(三)微信事件交互实例
Mar 02 NodeJs
详解Nodejs的timers模块
Dec 22 NodeJs
NodeJs安装npm包一直失败的解决方法
Apr 28 NodeJs
配置nodejs环境的方法
May 13 NodeJs
详解nodejs中express搭建权限管理系统
Sep 15 NodeJs
NodeJs搭建本地服务器之使用手机访问的实例讲解
May 12 NodeJs
基于Nodejs的Tcp封包和解包的理解
Sep 19 NodeJs
用Nodejs实现在终端中炒股的实现
Oct 18 NodeJs
在nodejs中创建child process的方法
Jan 26 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 函数学习简单小结
2010/07/08 PHP
基于PHP导出Excel的小经验 完美解决乱码问题
2013/06/10 PHP
基于PHP代码实现中奖概率算法可用于刮刮卡、大转盘等抽奖算法
2015/12/20 PHP
学习YUI.Ext 第四天--对话框Dialog的使用
2007/03/10 Javascript
js弹窗代码 可以指定弹出间隔
2010/07/03 Javascript
用Javascript实现Sleep暂停功能代码
2010/09/03 Javascript
JS重要知识点小结
2011/11/06 Javascript
使用jquery局部刷新(jquery.load)从数据库取出数据
2014/01/22 Javascript
JS实现鼠标点击展开或隐藏表格行的方法
2015/03/03 Javascript
使用jQuery UI库开发Web界面的简单入门指引
2016/04/22 Javascript
JS组件Bootstrap实现下拉菜单效果代码
2016/04/26 Javascript
浅谈js数组和splice的用法
2016/12/04 Javascript
jQuery命名空间与闭包用法示例
2017/01/12 Javascript
JavaScript该如何学习 怎样轻松学习JavaScript
2017/06/12 Javascript
详解如何在vue项目中使用lodop打印插件
2018/09/27 Javascript
vue在响应头response中获取自定义headers操作
2020/07/24 Javascript
使用Vue实现一个树组件的示例
2020/11/06 Javascript
[01:39](回顾)各路豪强针锋相对,几经鏖战四强产生
2014/07/01 DOTA
[46:40]VGJ.T vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
[54:26]完美世界DOTA2联赛PWL S3 Forest vs Rebirth 第一场 12.10
2020/12/12 DOTA
django创建自定义模板处理器的实例详解
2017/08/14 Python
Python图像处理实现两幅图像合成一幅图像的方法【测试可用】
2019/01/04 Python
Python帮你识破双11的套路
2019/11/11 Python
python itsdangerous模块的具体使用方法
2020/02/17 Python
python GUI库图形界面开发之PyQt5信号与槽机制、自定义信号基础介绍
2020/02/25 Python
使用HTML和CSS3绘制基本卡通图案的示例分享
2015/11/06 HTML / CSS
Farfetch阿联酋:奢侈品牌时尚购物平台
2019/07/26 全球购物
美国眼镜网站:LensCrafters
2020/01/19 全球购物
培训楼经理岗位责任制
2014/02/10 职场文书
优秀团队获奖感言
2014/02/19 职场文书
建议书的格式
2014/05/12 职场文书
军训拉歌口号
2014/06/13 职场文书
网络文明传播志愿者活动方案
2014/08/20 职场文书
开学典礼致辞
2015/07/29 职场文书
golang 比较浮点数的大小方式
2021/05/02 Golang
如何搭建 MySQL 高可用高性能集群
2021/06/21 MySQL