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与Mysql的交互示例代码
Aug 18 NodeJs
Nodejs+express+html5 实现拖拽上传
Aug 08 NodeJs
实例详解Nodejs 保存 payload 发送过来的文件
Jan 14 NodeJs
nodejs微信公众号支付开发
Sep 19 NodeJs
Nodejs 搭建简单的Web服务器详解及实例
Nov 30 NodeJs
详解nodejs中的process进程
Mar 19 NodeJs
NodeJs测试框架Mocha的安装与使用
Mar 28 NodeJs
nodejs操作mongodb的填删改查模块的制作及引入实例
Jan 02 NodeJs
详解nodejs 开发企业微信第三方应用入门教程
Mar 12 NodeJs
Nodejs 识别图片类型的方法
Aug 15 NodeJs
nodejs使用Sequelize框架操作数据库的实现
Oct 21 NodeJs
Nodejs 微信小程序消息推送的实现
Jan 20 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
PHP4实际应用经验篇(5)
2006/10/09 PHP
php获取远程文件内容的函数
2015/11/02 PHP
CentOS下搭建PHP环境与WordPress博客程序的全流程总结
2016/05/07 PHP
JavaScript 拾漏补遗
2009/12/27 Javascript
extjs ColumnChart设置不同的颜色实现代码
2013/05/17 Javascript
jquery性能优化高级技巧
2015/08/24 Javascript
三种带箭头提示框总结实例
2016/06/14 Javascript
jQuery Ajax 异步加载显示等待效果代码分享
2016/08/01 Javascript
JS原型与原型链的深入理解
2017/02/15 Javascript
JS文件/图片从电脑里面拖拽到浏览器上传文件/图片
2017/03/08 Javascript
JavaScript校验Number(4,1)格式的数字实例代码
2017/03/13 Javascript
js指定步长实现单方向匀速运动
2017/07/17 Javascript
vue+socket.io+express+mongodb 实现简易多房间在线群聊示例
2017/10/21 Javascript
Element-UI踩坑之Pagination组件的使用
2018/10/29 Javascript
JS实现盒子拖拽效果
2020/02/06 Javascript
element-ui table行点击获取行索引(index)并利用索引更换行顺序
2020/02/27 Javascript
详细分析Node.js 多进程
2020/06/22 Javascript
使用Vue-scroller页面input框不能触发滑动的问题及解决方法
2020/08/08 Javascript
Python中列表、字典、元组、集合数据结构整理
2014/11/20 Python
使用优化器来提升Python程序的执行效率的教程
2015/04/02 Python
tensorflow实现softma识别MNIST
2018/03/12 Python
如何在Cloud Studio上执行Python代码?
2019/08/09 Python
如何基于pythonnet调用halcon脚本
2020/01/20 Python
通过实例解析python描述符原理作用
2020/01/22 Python
python+appium+yaml移动端自动化测试框架实现详解
2020/11/24 Python
python操作toml文件的示例代码
2020/11/27 Python
网络技术专业求职信
2014/02/18 职场文书
《动手做做看》教学反思
2014/04/09 职场文书
大学生学期个人总结
2015/02/12 职场文书
2015年预算员工作总结
2015/05/14 职场文书
离婚被告答辩状
2015/05/22 职场文书
横空出世观后感
2015/06/09 职场文书
nginx限制并发连接请求数的方法
2021/04/01 Servers
python通配符之glob模块的使用详解
2021/04/24 Python
Pytest中skip和skipif的具体使用方法
2021/06/30 Python
Apache Hudi集成Spark SQL操作hide表
2022/03/31 Servers