NodeJS爬虫实例之糗事百科


Posted in NodeJs onDecember 14, 2017

1.前言分析

往常都是利用 Python/.NET 语言实现爬虫,然现在作为一名前端开发人员,自然需要熟练 NodeJS。下面利用 NodeJS 语言实现一个糗事百科的爬虫。另外,本文使用的部分代码是 es6 语法。

实现该爬虫所需要的依赖库如下。

request: 利用 get 或者 post 等方法获取网页的源码。 cheerio: 对网页源码进行解析,获取所需数据。

本文首先对爬虫所需依赖库及其使用进行介绍,然后利用这些依赖库,实现一个针对糗事百科的网络爬虫。

2. request 库

request 是一个轻量级的 http 库,功能十分强大且使用简单。可以使用它实现 Http 的请求,并且支持 HTTP 认证, 自定请求头等。下面对 request 库中一部分功能进行介绍。

安装 request 模块如下:

npm install request

在安装好 request 后,即可进行使用,下面利用 request 请求一下百度的网页。

const req = require('request');
req('http://www.baidu.com', (error, response, body) => {
 if (!error && response.statusCode == 200) {
 console.log(body)
 }
})

在没有设置 options 参数时,request 方法默认是 get 请求。而我喜欢利用 request 对象的具体方法,使用如下:

req.get({
 url: 'http://www.baidu.com'
},(err, res, body) => {
 if (!err && res.statusCode == 200) {
 console.log(body)
 }
});

然而很多时候,直接去请求一个网址所获取的 html 源码,往往得不到我们需要的信息。一般情况下,需要考虑到请求头和网页编码。

网页的请求头网页的编码

下面介绍在请求的时候如何添加网页请求头以及设置正确的编码。

req.get({
 url : url,
 headers: {
  "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
  "Host" : "www.zhihu.com",
  "Upgrade-Insecure-Requests" : "1"
 },
 encoding : 'utf-8'
}, (err, res, body)=>{
 if(!err)
  console.log(body);
})

设置 options 参数, 添加 headers 属性即可实现请求头的设置;添加 encoding 属性即可设置网页的编码。需要注意的是,若 encoding:null ,那么 get 请求所获取的内容则是一个 Buffer 对象,即 body 是一个 Buffer 对象。

上面介绍的功能足矣满足后面的所需了

3. cheerio 库

cheerio 是一款服务器端的 Jquery,以轻、快、简单易学等特点被开发者喜爱。有 Jquery 的基础后再来学习 cheerio 库非常轻松。它能够快速定位到网页中的元素,其规则和 Jquery 定位元素的方法是一样的;它也能以一种非常方便的形式修改 html 中的元素内容,以及获取它们的数据。下面主要针对 cheerio 快速定位网页中的元素,以及获取它们的内容进行介绍。

首先安装 cheerio 库

npm install cheerio

下面先给出一段代码,再对代码进行解释 cheerio 库的用法。对博客园首页进行分析,然后提取每一页中文章的标题。

首先对博客园首页进行分析。如下图:

NodeJS爬虫实例之糗事百科

对 html 源代码进行分析后,首先通过 .post_item 获取所有标题,接着对每一个 .post_item 进行分析,使用 a.titlelnk 即可匹配每个标题的 a 标签。下面通过代码进行实现。

const req = require('request');
const cheerio = require('cheerio');

req.get({
 url: 'https://www.cnblogs.com/'
 }, (err, res, body) => {
 if (!err && res.statusCode == 200) {
  let cnblogHtmlStr = body;
  let $ = cheerio.load(cnblogHtmlStr);
  $('.post_item').each((index, ele) => {
  let title = $(ele).find('a.titlelnk');
  let titleText = title.text();
  let titletUrl = title.attr('href');
  console.log(titleText, titletUrl);
  });
 }
 });

当然,cheerio 库也支持链式调用,上面的代码也可改写成:

let cnblogHtmlStr = body;
let $ = cheerio.load(cnblogHtmlStr);
let titles = $('.post_item').find('a.titlelnk');
titles.each((index, ele) => {
 let titleText = $(ele).text();
 let titletUrl = $(ele).attr('href');
 console.log(titleText, titletUrl);

上面的代码非常简单,就不再用文字进行赘述了。下面总结一点自己认为比较重要的几点。

使用 find() 方法获取的节点集合 A,若再次以 A 集合中的元素为根节点定位它的子节点以及获取子元素的内容与属性,需对 A 集合中的子元素进行 $(A[i]) 包装,如上面的$(ele) 一样。在上面代码中使用 $(ele) ,其实还可以使用 $(this) 但是由于我使用的是 es6 的箭头函数,因此改变了 each 方法中回调函数的 this 指针,因此,我使用 $(ele); cheerio 库也支持链式调用,如上面的 $('.post_item').find('a.titlelnk') ,需要注意的是,cheerio 对象 A 调用方法 find(),如果 A 是一个集合,那么 A 集合中的每一个子元素都调用 find() 方法,并放回一个结果结合。如果 A 调用 text() ,那么 A 集合中的每一个子元素都调用 text() 并返回一个字符串,该字符串是所有子元素内容的合并(直接合并,没有分隔符)。

最后在总结一些我比较常用的方法。

first() last() children([selector]): 该方法和 find 类似,只不过该方法只搜索子节点,而 find 搜索整个后代节点。

4. 糗事百科爬虫

通过上面对 requestcheerio 类库的介绍,下面利用这两个类库对糗事百科的页面进行爬取。

1、在项目目录中,新建 httpHelper.js 文件,通过 url 获取糗事百科的网页源码,代码如下:

//爬虫
const req = require('request');

function getHtml(url){
 return new Promise((resolve, reject) => {
  req.get({
   url : url,
   headers: {
    "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
    "Referer" : "https://www.qiushibaike.com/"
   },
   encoding : 'utf-8'
  }, (err, res, body)=>{
   if(err) reject(err);
   else resolve(body);
  })
 });
}
exports.getHtml = getHtml;

2、在项目目录中,新建一个 Splider.js 文件,分析糗事百科的网页代码,提取自己需要的信息,并且建立一个逻辑通过更改 url 的 id 来爬取不同页面的数据。

const cheerio = require('cheerio');
const httpHelper = require('./httpHelper');
function getQBJok(htmlStr){
 let $ = cheerio.load(htmlStr);
 let jokList = $('#content-left').children('div');
 let rst = [];
 jokList.each((i, item)=>{
  let node = $(item);
  let titleNode = node.find('h2');
  let title = titleNode ? titleNode.text().trim() : '匿名用户';
  let content = node.find('.content span').text().trim();
  let likeNumber = node.find('i[class=number]').text().trim();
  rst.push({
   title : title,
   content : content,
   likeNumber : likeNumber
  });
 });
 return rst;
}
async function splider(index = 1){
 let url = `https://www.qiushibaike.com/8hr/page/${index}/`;
 let htmlStr = await httpHelper.getHtml(url);
 let rst = getQBJok(htmlStr);
 return rst;
}
splider(1);

在获取糗事百科网页信息的时候,首先在浏览器中对源码进行分析,定位到自己所需要标签,然后提取标签的文本或者属性值,这样就完成了网页的解析。

Splider.js 文件入口是 splider 方法,首先根据传入该方法的 index 索引,构造糗事百科的 url,接着获取该 url 的网页源码,最后将获取的源码传入 getQBJok 方法,进行解析,本文只解析每条文本笑话的作者、内容以及喜欢个数。

直接运行 Splider.js 文件,即可爬取第一页的笑话信息。然后可以更改 splider 方法的参数,实现抓取不同页面的信息。

在上面已有代码的基础上,使用 koavue2.0 搭建一个浏览文本的页面,效果如下:

NodeJS爬虫实例之糗事百科

源码已上传到 github 上。下载地址:https://github.com/StartAction/SpliderQB ;

项目运行依赖 node v7.6.0 以上, 首先从 Github 上面克隆整个项目。

git clone https://github.com/StartAction/SpliderQB.git

克隆之后,进入项目目录,运行下面命令即可。

node app.js

5. 总结

通过实现一个完整的爬虫功能,加深自己对 Node 的理解,且实现的部分语言都是使用 es6 的语法,让自己加快对 es6 语法的学习进度。另外,在这次实现中,遇到了 Node 的异步控制的知识,本文是采用的是 asyncawait 关键字,也是我最喜欢的一种,然而在 Node 中,实现异步控制有好几种方式。关于具体的方式以及原理,有时间再进行总结。

NodeJs 相关文章推荐
NodeJS Web应用监听sock文件实例
Feb 18 NodeJs
Windows系统下使用Sublime搭建nodejs环境
Apr 13 NodeJs
NodeJS中的MongoDB快速入门详细教程
Nov 11 NodeJs
angular2+nodejs实现图片上传功能
Mar 27 NodeJs
NodeJs使用Mysql模块实现事务处理实例
May 31 NodeJs
nodejs构建本地web测试服务器 如何解决访问静态资源问题
Jul 14 NodeJs
详解使用vscode+es6写nodejs服务端调试配置
Sep 21 NodeJs
nodejs实现爬取网站图片功能
Dec 14 NodeJs
使用npm安装最新版本nodejs
Jan 18 NodeJs
nodejs中Express与Koa2对比分析
Feb 06 NodeJs
Nodejs监听日志文件的变化的过程解析
Aug 04 NodeJs
Nodejs + sequelize 实现增删改查操作
Nov 07 NodeJs
nodejs实现爬取网站图片功能
Dec 14 #NodeJs
NodeJs form-data格式传输文件的方法
Dec 13 #NodeJs
nodejs实现截取上传视频中一帧作为预览图片
Dec 10 #NodeJs
nodejs实现大文件(在线视频)的读取
Oct 16 #NodeJs
nodejs发送http请求时遇到404长时间未响应的解决方法
Dec 10 #NodeJs
NodeJs实现定时任务的示例代码
Dec 05 #NodeJs
windows系统下更新nodejs版本的方案
Nov 24 #NodeJs
You might like
php下用GD生成生成缩略图的两个选择和区别
2007/04/17 PHP
Zend studio for eclipse中使php可以调用mysql相关函数的设置方法
2008/10/13 PHP
smarty内置函数section的用法
2015/01/22 PHP
PHP Echo字符串的连接格式
2016/03/07 PHP
php curl 模拟登录并获取数据实例详解
2016/12/22 PHP
php设计模式之工厂模式用法经典实例分析
2019/09/20 PHP
jQuery EasyUI API 中文文档 - DataGrid数据表格
2011/11/17 Javascript
背景图跟随鼠标移动的Mootools插件实现代码
2011/12/12 Javascript
jsvascript图像处理—(计算机视觉应用)图像金字塔
2013/01/15 Javascript
你必须知道的Javascript知识点之"this指针"的应用
2013/04/23 Javascript
神奇!js+CSS+DIV实现文字颜色渐变效果
2016/03/16 Javascript
jQuery实现限制文本框的输入长度
2017/01/11 Javascript
微信小程序Redux绑定实例详解
2017/06/07 Javascript
JavaScript实现随机数生成器(去重)
2017/10/13 Javascript
Nodejs连接mysql并实现增、删、改、查操作的方法详解
2018/01/04 NodeJs
简单了解JavaScript弹窗实现代码
2020/05/07 Javascript
24个ES6方法解决JS实际开发问题(小结)
2020/05/31 Javascript
Python中使用 Selenium 实现网页截图实例
2014/07/18 Python
python绘制双柱形图代码实例
2017/12/14 Python
pandas.dataframe中根据条件获取元素所在的位置方法(索引)
2018/06/07 Python
python 获取utc时间转化为本地时间的方法
2018/12/31 Python
Python3.5实现的罗马数字转换成整数功能示例
2019/02/25 Python
python基于SMTP协议发送邮件
2019/05/31 Python
在python中,使用scatter绘制散点图的实例
2019/07/03 Python
Python中*args和**kwargs的区别详解
2019/09/17 Python
opencv+python实现鼠标点击图像,输出该点的RGB和HSV值
2020/06/02 Python
python批量修改交换机密码的示例
2020/09/22 Python
Python reversed反转序列并生成可迭代对象
2020/10/22 Python
前厅收银主管岗位职责
2014/02/04 职场文书
《桃花心木》教学反思
2014/02/17 职场文书
消防安全责任书
2014/04/14 职场文书
社区先进事迹材料
2014/05/19 职场文书
个人工作总结范文2014
2014/11/07 职场文书
教师节慰问信
2015/02/15 职场文书
聘任协议书(挂靠)
2015/09/21 职场文书
Redis数据同步之redis shake的实现方法
2022/04/21 Redis