前言 & 初衷
- 最近自己在写一个前端项目的,由于没有后端提供数据接口,只能自己伪造一些数据;但是你会发现伪造数据也需要消耗很大的精力;Oh My God! 难道我们前端就没有办法自己完成一个项目了吗?
- <font color=red face="微软雅黑">于是我便想为什么不自己去写一个爬虫,去爬去我想要的数据呢?</font>由于我是前端于是便打算使用<font color=red face="微软雅黑">Node.js</font>来写一个爬虫;终于在捣鼓了一段时间后我发现了一个很简单的爬取网页上数据的方法,这种方法不管是静态网页还是动态网页的数据都可以爬取。
- 为了不让其他前端小伙伴在自己写前端项目时候陷入像我一样的困境,于是便打算写这篇文章把我使用的方法分享出来。希望能对同样在学习前端的小伙伴有所帮助!
准备工具
- VSCode 编码工具 (VSCode是一个轻量级但是十分强大的源代码编辑器)
- Node.js 开发环境 (Node.js采用事件驱动、异步编程,为网络服务而设计,是目前速度最快的 Javascript引擎。)
- cheerio 操作DOM
node环境安装: cnpm i cheerio -S
cheerio 是为服务器特别定制的,快速、灵活、实施的jQuery核心实现,<br/>
了解 cheerio<br/>
英文:https://github.com/cheeriojs/...<br/>
中文:https://cnodejs.org/topic/520...
- nightmare (<font color=red face="微软雅黑">自动化测试工具</font>)
node环境安装: cnpm i nightmare -S
nightmare 是一个基于Electron的框架,针对Web自动化测试和爬虫,因为其具有跟PlantomJS一样的自动化测试的功能可以在页面上模拟用户的行为触发一些异步数据加载,也可以跟Request库一样直接访问URL来抓取数据,并且可以设置页面的延迟时间,所以无论是手动触发脚本还是行为触发脚本都是轻而易举的。
"三步走" 流程分析
- 第一步: 确定目标网页;
今日头条:https://www.toutiao.com/
<br/>
- 第二步: 确定目标数据区域,分析Dom结构;<br/>
- 第三步: 安装依赖、编码->测试->爬取数据;<br/>
cnpm i cheerio -S
<br/>cnpm i nightmare -S
开始Coding!也分三步实现!!!
第一步:引入所需要的第三方包
const cheerio = require('cheerio');
const fs = require('fs'); // node.js自带的写模块,后面会用来将爬好的数据写到你的根文件夹中
const Nightmare = require('nightmare');
const nightmare = Nightmare({show: true}); // show:true 显示内置模拟浏览器
var newsList = [] // 用来存新闻数据
第二步:使用nightmare来爬取目标网页的数据到控制台来
nightmare工具 API介绍:https://github.com/segmentio/...
nightmare
.goto('https://www.toutiao.com/')
.wait(6000) // 6000 的意思是当模拟浏览器打开后会等待6秒再爬取页面数据,有这段时间就可以做你想要的动态加载操作
.evaluate(() => document.querySelector("div.feed-infinite-wrapper").innerHTML) // 这里一定要取到你想要爬取数据区域的容器的类名
.then(htmlStr => {
newsListData = getnewsList(htmlStr) // getnewsList() 函数将执行对原始数据的选择和处理
newsListData = JSON.stringify(newsListData) // 将数据转化为字符串,以便进行fs写文件操作
fs.writeFile("newsListData.json", newsListData, "utf-8", (error) => {
//监听错误,如正常输出,则打印null
if (error == null) {
console.log("恭喜您,newsListData数据爬取成功!)");
}
})
})
.catch(error => {
console.log(`数据抓取失败 - ${error}`);
})
值得说的一点是:
es7的 async/await据说是异步编程的终级解决方案,它可以让我们以同步的思维方式来进行异步编程。es6的 Promise解决了异步编程的“回调地狱”,async/await同时使异步流程控制变得友好而有清晰,有兴趣的同学可以去了解学习一下,真的很好用。
第三步:封装getnewsList()处理原始数据
let getnewsList = (res) => {
if (res) {
let $ = cheerio.load(res);
$('ul li').each(function (index, item) {
if ($(item).children().attr('class') === 'no-mode') {
let titem = $(item).children('.no-mode').children('.title-box');
let fitem = $(item).children('.no-mode').children('.footer-bar');
let news1 = {
title_href: titem.children().attr('href'), // attr('属性') 可以取到对应属性的值,包括行内样式的值
title_text: titem.children().text(), // text() 取到文本内容
tag_href: fitem.children('.footer-bar-left').children('.tag').attr('href'),
tag_text: fitem.children('.footer-bar-left').children('.tag').text(),
mediaAvatar_href: fitem.children('.footer-bar-left').children('.media-avatar').attr('href'),
mediaAvatar_imgsrc: fitem.children('.footer-bar-left').children('.media-avatar').children().first().attr('src'),
source1_href: fitem.children('.footer-bar-left').children('.source').first().attr('href'),
source1_text: fitem.children('.footer-bar-left').children('.source').first().text(),
source2_href: fitem.children('.footer-bar-left').children('.source').last().attr('href'),
source2_text: fitem.children('.footer-bar-left').children('.source').last().text(),
time_text: fitem.children('.footer-bar-left').children('span').text()
}
newsList.push(news1)
} else {
let litem = $(item).children('.single-mode').children('.single-mode-lbox');
let ritem = $(item).children('.single-mode').children('.single-mode-rbox').children('.single-mode-rbox-inner');
let news2 = {
img_href: litem.children('.img-wrap').attr('href'),
img_src: litem.children('.img-wrap').children().attr('src'),
title_href: ritem.children('.title-box').children().attr('href'),
title_text: ritem.children('.title-box').children().text(),
tag_href: ritem.children('.footer-bar').children('.footer-bar-left').children('.tag').attr('href'),
tag_text: ritem.children('.footer-bar').children('.footer-bar-left').children('.tag').text(),
mediaAvatar_href: ritem.children('.footer-bar').children('.footer-bar-left').children('.media-avatar').attr('href'),
mediaAvatar_imgsrc: ritem.children('.footer-bar').children('.footer-bar-left').children('.media-avatar').children().attr('src'),
source1_href: ritem.children('.footer-bar').children('.footer-bar-left').children('.source').first().attr('href'),
source1_text: ritem.children('.footer-bar').children('.footer-bar-left').children('.source').first().text(),
source2_href: ritem.children('.footer-bar').children('.footer-bar-left').children('.source').last().attr('href'),
source2_text: ritem.children('.footer-bar').children('.footer-bar-left').children('.source').last().text(),
time_text: ritem.children('.footer-bar').children('.footer-bar-left').children('span').text()
}
newsList.push(news2)
}
})
return newsList
} else {
console.log('无数据传入!')
return
}
}
关于cheerio的使用技巧
想要深入了解cheerio可以回到本文章前面,提供了学习链接;<br/>
个人比较喜欢使用cheerio的children()来选择想要拉取数据的值或者属性,只要一层一层抽丝剥茧的来取你想要的文本或者属性的值,就一定可以取到!<br/>
<font color=red face="微软雅黑">注意:</font> 如果你想要爬取的数据不在DOM结构上而在.css文件中,cheerio是不能选择到的,也就是说不能爬取样式,行内样式除外!可以使用attr('style')来取到字符串,再对字符串切割就可以取到你想要取到的值了!<br/>
<font color=red face="微软雅黑">到这里我们已经可以爬取今日头条新闻列表的数据了,但请看官再停留一会,容我来捋一捋网络爬虫的爬取数据的思路,请往下看!重要的是思路!</font>
网络爬虫实现爬取数据四大步骤!
一、确定目标网页,对目标数据区域分析
一般网页上的数据由静态和需动态加载的两种组成。现在我们以爬取今日头条新闻列表为例:
(可以看到今日头条新闻列表的数据是静态数据加载完再加载的!所以我们通过nightmare来模拟浏览器加载数据的动作。)<br/>
二、接下来,对目标数据区域分析
(可以看到再目标数据区域的列表中有两种不同的DOM结构,我们再对这两种不同的列表DOM结构进行不同的处理,最终把数据全部放到一个数组中)<br/>
三、接下来我们对其中一种数据列表DOM结构进行分析并处理
首先来分析结构,请看图。<br/>
(展示列表分析)
(浏览器源代码结构分析)
我们来对分析的结构使用cheerio来抓取想要的数据
let $ = cheerio.load(res);
$('ul li').each(function (index, item) {
if($(item).children().attr('class') === 'no-mode') {
// 这里的分析思路和下面分析一样...
}
// 这里是对分析结构的数据抓取
else {
let litem = $(item).children('.single-mode').children('.single-mode-lbox');
let ritem = $(item).children('.single-mode').children('.single-mode-rbox').children('.single-mode-rbox-inner');
let news2 = {
img_href: litem.children('.img-wrap').attr('href'),
img_src: litem.children('.img-wrap').children().attr('src'),
title_href: ritem.children('.title-box').children().attr('href'),
title_text: ritem.children('.title-box').children().text(),
tag_href: ritem.children('.footer-bar').children('.footer-bar-left').children('.tag').attr('href'),
tag_text: ritem.children('.footer-bar').children('.footer-bar-left').children('.tag').text(),
mediaAvatar_href: ritem.children('.footer-bar').children('.footer-bar-left').children('.media-avatar').attr('href'),
mediaAvatar_imgsrc: ritem.children('.footer-bar').children('.footer-bar-left').children('.media-avatar').children().attr('src'),
source1_href: ritem.children('.footer-bar').children('.footer-bar-left').children('.source').first().attr('href'),
source1_text: ritem.children('.footer-bar').children('.footer-bar-left').children('.source').first().text(),
source2_href: ritem.children('.footer-bar').children('.footer-bar-left').children('.source').last().attr('href'),
source2_text: ritem.children('.footer-bar').children('.footer-bar-left').children('.source').last().text(),
time_text: ritem.children('.footer-bar').children('.footer-bar-left').children('span').text()
}
newsList.push(news2)
}
}
四、抓取到的数据通过getnewsList()函数进行逻辑处理,(return)返回需要的目标数据到这里,我们再对这个返回的数据处理一下,通过Node.js自带的写模块写到你的Vscode里展示。
...
newsListData = getnewsList(htmlStr)
newsListData = JSON.stringify(newsListData)
fs.writeFile("newsListData.json", newsListData, "utf-8", (error) => {
//监听错误,如正常输出,则打印null
if (error == null) {
console.log("恭喜您,newsListData数据爬取成功!)");
}
})
...
最终我们可以看到爬取数据成功后在vscode上显示如下图:
(如果想要加载更多列表数据,在6s内往下拉刷新出更多数据出来就可以了。)
(这里我在Vscode里使用了JSON Tools插件格式化了爬取到的数据,使数据更便于浏览!)
github源码(欢迎star!)
总结
至此,一个完整的爬虫就写完了,其实使用node.js制作爬虫,还有很多方法,可以使用node.js的http模块或者第三方包superagent来爬取网页上的数据,他们相同的地方是都要用cheerio来处理数据,都只能爬取静态网页的数据,但是本文使用的第三方包nightmare,除了可以爬取静态的数据还能通过模拟浏览器来模拟动态加载的过程进而爬取动态的数据。到了这里相信各位看官已经知道了一个简单的爬虫是怎么写的了吧,是不是看完之后感觉爬虫也就那么回事,哈哈哈,让我们一起来对爬虫<font color=red face="微软雅黑">说So easy吧!!!</font>(各位看官如果喜欢本文的欢迎点赞收藏!你们的鼓励会使我更有动力,当然,如果发现我的方法还有缺陷或改进之处,欢迎评论!)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。