十分感谢你们的点赞和关注。其实,这是我第一次在
掘金
上写文章。由于我也是前段时间偶然之间才开始了解和学习爬虫,并且学习node的时间也不是很长。虽然用node作过一些后端的项目,但其实在node和爬虫方面我仍是一个新人,这篇文章主要是想和你们分享一下node和爬虫方面的基本知识,但愿对你们有帮助,也想和你们一块儿交流,一块儿学习,再次谢谢你们的支持!
对了,我开通了我的的 GitHub主页,里面有本身的技术文章,还会有我的的随想、思考和日志。之后全部的文章都会第一时间更新到这里,而后同步到其余平台。有喜欢的朋友能够没事去逛逛,再次感谢你们的支持!javascript
网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF
社区中间,更常常的称为网页追逐者),是一种按照必定的规则,自动地抓取万维网信息的程序或者脚本。另一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。
WIKIPEDIA 爬虫介绍
爬行对象从一些
种子URL
扩充到整个 Web,主要为门户站点搜索引擎和大型 Web 服务提供商采集数据。
是
指选择性
地爬行那些与预先定义好的主题相关页面的网络爬虫。
指对已下载网页采起增量式更新和
只爬行新产生的或者已经发生变化网页
的爬虫,它可以在必定程度上保证所爬行的页面是尽量新的页面。
爬行对象是一些在用户填入关键字搜索或登陆后才能访问到的
深层网页信息
的爬虫。
深度优先策略、广度优先策略
基于内容评价的爬行策略(内容相关性),基于连接结构评价的爬行策略、基于加强学习的爬行策略(连接重要性),基于语境图的爬行策略(距离,图论中两节点间边的权重)
统一更新法、个体更新法、基于分类的更新法、自适应调频更新法
Deep Web 爬虫爬行过程当中最重要部分就是表单填写,包含两种类型:基于领域知识的表单填写、基于网页结构分析的表单填写
现代的网页爬虫的行为一般是四种策略组合的结果:html
选择策略:决定所要下载的页面;
从新访问策略:决定何时检查页面的更新变化;
平衡礼貌策略:指出怎样避免站点超载;
并行策略:指出怎么协同达到分布式抓取的效果;
百度新闻 ( news.baidu.com/)
······
node.js (express)
+SublimeText 3
coding ···
1.在合适的磁盘目录下建立项目目录baiduNews
(个人项目目录是:F:\web\baiduNews
)
注:由于在写这篇文章的时候用的电脑真心比较渣。安装WebStorm或者VsCode跑项目有些吃力。因此后面的命令行操做我都是在Window自带的DOS命令行窗口中执行的。前端
1.在DOS命令行中进入项目根目录baiduNews
2.执行npm init
,初始化package.json
文件
express
(使用express来搭建一个简单的Http服务器。固然,你也可使用node中自带的http
模块)
superagent
(superagent是node里一个很是方便的、轻量的、渐进式的第三方客户端请求代理模块,用他来请求目标页面)
cheerio
(cheerio至关于node版的jQuery,用过jQuery的同窗会很是容易上手。它主要是用来获取抓取到的页面元素和其中的数据信息)
// 我的比较喜欢使用yarn来安装依赖包,固然你也可使用 npm install 来安装依赖,看我的习惯。
yarn add express yarn add superagent yarn add cheerio复制代码
依赖安装完成后你能够在package.json中查看刚才安装的依赖是否成功。
安装正确后以下图:java
1、使用express
启动一个简单的本地Http服务器node
一、在项目根目录下建立index.js
文件(后面都会在这个index文件中进行coding)git
二、建立好index.js
后,咱们首先实例化一个express
对象,用它来启动一个本地监听3000
端口的Http服务。程序员
const express = require('express');
const app = express();
// ...
let server = app.listen(3000, function () {
let host = server.address().address;
let port = server.address().port;
console.log('Your App is running at http://%s:%s', host, port);
});复制代码
对,就是这么简单,不到10行代码,搭建启动一个简单的本地Http服务。github
三、按照国际惯例,咱们但愿在访问本机地址http://localhost:3000
的时候,这个服务能给咱们犯规一个Hello World!
在index.js
中加入以下代码:web
app.get('/', function (req, res) {
res.send('Hello World!');
});复制代码
此时,在DOS中项目根目录baiduNews
下执行node index.js
,让项目跑起来。以后,打开浏览器,访问http://localhost:3000
,你就会发现页面上显示'Hellow World!'字样。
这样,在后面咱们获取到百度新闻首页的信息后,就能够在访问http://localhost:3000
时看到这些信息。
2、抓取百度新闻首页的新闻信息ajax
一、 首先,咱们先来分析一下百度新闻首页的页面信息。
百度新闻首页大致上分为“热点新闻”、“本地新闻”、“国内新闻”、“国际新闻”......等。此次咱们先来尝试抓取左侧“热点新闻”
和下方的“本地新闻”
两处的新闻数据。
F12
打开Chrome
的控制台,审查页面元素,通过查看左侧“热点新闻”信息所在DOM
的结构,咱们发现全部的“热点新闻”信息(包括新闻标题和新闻页面连接)都在id
为#pane-news
的<div
>下面<ul>
下<li>
下的<a>
标签中。用jQuery
的选择器表示为:#pane-news ul li a
。
二、为了爬取新闻数据,首先咱们要用superagent请求目标页面,获取整个新闻首页信息
// 引入所须要的第三方包
const superagent= require('superagent');
let hotNews = []; // 热点新闻
let localNews = []; // 本地新闻
/** * index.js * [description] - 使用superagent.get()方法来访问百度新闻首页 */
superagent.get('http://news.baidu.com/').end((err, res) => {
if (err) {
// 若是访问失败或者出错,会这行这里
console.log(`热点新闻抓取失败 - ${err}`)
} else {
// 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
// 抓取热点新闻数据
hotNews = getHotNews(res)
}
});复制代码
三、获取页面信息后,咱们来定义一个函数getHotNews()
来抓取页面内的“热点新闻”数据。
/** * index.js * [description] - 抓取热点新闻页面 */
// 引入所须要的第三方包
const cheerio = require('cheerio');
let getHotNews = (res) => {
let hotNews = [];
// 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res.text中。
/* 使用cheerio模块的cherrio.load()方法,将HTMLdocument做为参数传入函数 之后就可使用相似jQuery的$(selectior)的方式来获取页面元素 */
let $ = cheerio.load(res.text);
// 找到目标数据所在的页面元素,获取数据
$('div#pane-news ul li a').each((idx, ele) => {
// cherrio中$('selector').each()用来遍历全部匹配到的DOM元素
// 参数idx是当前遍历的元素的索引,ele就是当前便利的DOM元素
let news = {
title: $(ele).text(), // 获取新闻标题
href: $(ele).attr('href') // 获取新闻网页连接
};
hotNews.push(news) // 存入最终结果数组
});
return hotNews
};复制代码
这里要多说几点:
async/await
听说是异步编程的终级解决方案,它可让咱们以同步的思惟方式来进行异步编程。Promise
解决了异步编程的“回调地狱”,async/await同时使异步流程控制变得友好而有清晰,有兴趣的同窗能够去了解学习一下,真的很好用。superagent
模块提供了不少好比get
、post
、delte
等方法,能够很方便地进行Ajax请求操做。在请求结束后执行.end()
回调函数。.end()
接受一个函数做为参数,该函数又有两个参数error和res
。当请求失败,error
会包含返回的错误信息,请求成功,error
值为null
,返回的数据会包含在res
参数中。cheerio
模块的.load()
方法,将HTML document
做为参数传入函数,之后就可使用相似jQuery的$(selectior)的方式来获取页面元素。同时可使用相似于jQuery
中的.each()
来遍历元素。此外,还有不少方法,你们能够自行Google/Baidu。
四、将抓取的数据返回给前端浏览器
前面,const app = express();
实例化了一个express
对象app
。
app.get('', async() => {})
接受两个参数,第一个参数接受一个String类型的路由路径,表示Ajax的请求路径。第二个参数接受一个函数Function,当请求此路径时就会执行这个函数中的代码。
/** * [description] - 跟路由 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
res.send(hotNews);
});复制代码
在DOS中项目根目录baiduNews
下执行node index.js
,让项目跑起来。以后,打开浏览器,访问http://localhost:3000
,你就会发现抓取到的数据返回到了前端页面。我运行代码后浏览器展现的返回信息以下:
注:由于个人Chrome
安装了JSONView扩展程序,因此返回的数据在页面展现的时候会被自动格式化为结构性的JSON格式,方便查看。
OK!!这样,一个简单的百度“热点新闻”
的爬虫就大功告成啦!!
简单总结一下,其实步骤很简单:
express
启动一个简单的Http
服务- 分析目标页面
DOM
结构,找到所要抓取的信息的相关DOM
元素- 使用
superagent
请求目标页面- 使用
cheerio
获取页面元素,获取目标数据- 返回数据到前端浏览器
如今,继续咱们的目标,抓取“本地新闻”
数据(编码过程当中,咱们会遇到一些有意思的问题)
有了前面的基础,咱们天然而然的会想到利用和上面相同的方法“本地新闻”数据。
一、 分析页面中“本地新闻”部分的DOM
结构,以下图:
F12
打开控制台,审查“本地新闻”DOM
元素,咱们发现,“本地新闻”分为两个主要部分,“左侧新闻”和右侧的“新闻资讯”。这全部目标数据都在id
为#local_news
的div
中。“左侧新闻”数据又在id
为#localnews-focus
的ul
标签下的li
标签下的a
标签中,包括新闻标题和页面连接。“本地资讯”数据又在id
为#localnews-zixun
的div
下的ul
标签下的li
标签下的a
标签中,包括新闻标题和页面连接。
二、OK!分析了DOM
结构,肯定了数据的位置,接下来和爬取“热点新闻”
同样,循序渐进,定义一个getLocalNews()
函数,爬取这些数据。
/** * [description] - 抓取本地新闻页面 */
let getLocalNews = (res) => {
let localNews = [];
let $ = cheerio.load(res);
// 本地新闻
$('ul#localnews-focus li a').each((idx, ele) => {
let news = {
title: $(ele).text(),
href: $(ele).attr('href'),
};
localNews.push(news)
});
// 本地资讯
$('div#localnews-zixun ul li a').each((index, item) => {
let news = {
title: $(item).text(),
href: $(item).attr('href')
};
localNews.push(news);
});
return localNews
};复制代码
对应的,在superagent.get()
中请求页面后,咱们须要调用getLocalNews()
函数,来爬去本地新闻数据。superagent.get()
函数修改成:
superagent.get('http://news.baidu.com/').end((err, res) => {
if (err) {
// 若是访问失败或者出错,会这行这里
console.log(`热点新闻抓取失败 - ${err}`)
} else {
// 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
// 抓取热点新闻数据
hotNews = getHotNews(res)
localNews = getLocalNews(res)
}
});复制代码
同时,咱们要在app.get()
路由中也要将数据返回给前端浏览器。app.get()
路由代码修改成:
/** * [description] - 跟路由 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
res.send({
hotNews: hotNews,
localNews: localNews
});
});复制代码
编码完成,激动不已!!DOS
中让项目跑起来,用浏览器访问http://localhost:3000
尴尬的事情发生了!!返回的数据只有热点新闻,而本地新闻返回一个空数组[ ]
。检查代码,发现也没有问题,但为何一直返回的空数组呢?
通过一番缘由查找,才返现问题出在哪里!!
为了找到缘由,首先,咱们看看用superagent.get('http://news.baidu.com/').end((err, res) => {})
请求百度新闻首页在回调函数.end()
中的第二个参数res中到底拿到了什么内容?
// 新定义一个全局变量 pageRes
let pageRes = {}; // supergaent页面返回值
// superagent.get()中将res存入pageRes
superagent.get('http://news.baidu.com/').end((err, res) => {
if (err) {
// 若是访问失败或者出错,会这行这里
console.log(`热点新闻抓取失败 - ${err}`)
} else {
// 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
// 抓取热点新闻数据
// hotNews = getHotNews(res)
// localNews = getLocalNews(res)
pageRes = res
}
});
// 将pageRes返回给前端浏览器,便于查看
app.get('/', async (req, res, next) => {
res.send({
// {}hotNews: hotNews,
// localNews: localNews,
pageRes: pageRes
});
});复制代码
访问浏览器
http://localhost:3000
,页面展现以下内容:
能够看到,返回值中的text
字段应该就是整个页面的HTML
代码的字符串格式。为了方便咱们观察,能够直接把这个text
字段值返回给前端浏览器,这样咱们就可以清晰地看到通过浏览器渲染后的页面。
修改给前端浏览器的返回值
app.get('/', async (req, res, next) => {
res.send(pageRes.text)
}复制代码
访问浏览器http://localhost:3000
,页面展现以下内容:
审查元素才发现,原来咱们抓取的目标数据所在的DOM
元素中是空的,里面没有数据!
到这里,一切水落石出!在咱们使用superagent.get()
访问百度新闻首页时,res
中包含的获取的页面内容中,咱们想要的“本地新闻”数据尚未生成,DOM
节点元素是空的,因此出现前面的状况!抓取后返回的数据一直是空数组[ ]
。
在控制台的Network
中咱们发现页面请求了一次这样的接口:
http://localhost:3000/widget?id=LocalNews&ajax=json&t=1526295667917
,接口状态404
。
这应该就是百度新闻获取“本地新闻”
的接口,到这里一切都明白了!“本地新闻”是在页面加载后动态请求上面这个接口获取的,因此咱们用superagent.get()
请求的页面再去请求这个接口时,接口URL
中hostname
部分变成了本地IP
地址,而本机上没有这个接口,因此404
,请求不到数据。
找到缘由,咱们来想办法解决这个问题!!
- 直接使用
superagen
t访问正确合法的百度“本地新闻”
的接口,获取数据后返回给前端浏览器。- 使用第三方
npm
包,模拟浏览器访问百度新闻首页,在这个模拟浏览器中当“本地新闻”
加载成功后,抓取数据,返回给前端浏览器。
以上方法都可,咱们来试试比较有意思的第二种方法
Electron
可让你使用纯JavaScript
调用Chrome
丰富的原生的接口来创造桌面应用。你能够把它看做一个专一于桌面应用的Node.js
的变体,而不是Web
服务器。其基于浏览器的应用方式能够极方便的作各类响应式的交互
Nightmare
是一个基于Electron
的框架,针对Web
自动化测试和爬虫,由于其具备跟PlantomJS
同样的自动化测试的功能能够在页面上模拟用户的行为触发一些异步数据加载,也能够跟Request
库同样直接访问URL
来抓取数据,而且能够设置页面的延迟时间,因此不管是手动触发脚本仍是行为触发脚本都是垂手可得的。
// 安装nightmare
yarn add nightmare复制代码
给index.js
中新增以下代码:
const Nightmare = require('nightmare'); // 自动化测试包,处理动态页面
const nightmare = Nightmare({ show: true }); // show:true 显示内置模拟浏览器
/** * [description] - 抓取本地新闻页面 * [nremark] - 百度本地新闻在访问页面后加载js定位IP位置后获取对应新闻, * 因此抓取本地新闻须要使用 nightmare 一类的自动化测试工具, * 模拟浏览器环境访问页面,使js运行,生成动态页面再抓取 */
// 抓取本地新闻页面
nightmare
.goto('http://news.baidu.com/')
.wait("div#local_news")
.evaluate(() => document.querySelector("div#local_news").innerHTML)
.then(htmlStr => {
// 获取本地新闻数据
localNews = getLocalNews(htmlStr)
})
.catch(error => {
console.log(`本地新闻抓取失败 - ${error}`);
})复制代码
修改getLocalNews()
函数为:
/** * [description]- 获取本地新闻数据 */
let getLocalNews = (htmlStr) => {
let localNews = [];
let $ = cheerio.load(htmlStr);
// 本地新闻
$('ul#localnews-focus li a').each((idx, ele) => {
let news = {
title: $(ele).text(),
href: $(ele).attr('href'),
};
localNews.push(news)
});
// 本地资讯
$('div#localnews-zixun ul li a').each((index, item) => {
let news = {
title: $(item).text(),
href: $(item).attr('href')
};
localNews.push(news);
});
return localNews
}复制代码
修改app.get('/')
路由为:
/** * [description] - 跟路由 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
res.send({
hotNews: hotNews,
localNews: localNews
})
});复制代码
此时,DOS
命令行中从新让项目跑起来,浏览器访问https://localhost:3000
,看看页面展现的信息,看是否抓取到了“本地新闻”
数据!
至此,一个简单而又完整的抓取百度新闻页面“热点新闻”和“本地新闻”的爬虫就大功告成啦!!
最后总结一下,总体思路以下:
express
启动一个简单的Http
服务- 分析目标页面
DOM
结构,找到所要抓取的信息的相关DOM元
素- 使用
superagent
请求目标页面- 动态页面(须要加载页面后运行
JS
或请求接口的页面)可使用Nightmare
模拟浏览器访问- 使用
cheerio
获取页面元素,获取目标数据
爬虫完整代码GitHub地址: 完整代码
并发控制
和反-反爬虫
的一些策略。再用爬虫取爬去一些须要登陆和输入验证码的网站,欢迎到时你们关注和指正交流。
再次感谢你们的点赞和关注和评论,谢谢你们的支持,谢谢!我本身以为我算是一个爱文字,爱音乐,同时也喜欢coding的半文艺程序员。
以前也一直想着写一写技术性和其余偏文学性的文章。虽然本身的底子没有多么优秀,但老是以为在写文章的过程当中,不管是技术性的仍是偏文学性的,这个过程当中能够督促本身去思考,督促本身去学习和交流。毕竟天天忙忙碌碌之余,仍是要活出本身不同的生活。
因此,之后若是有一些好的文章我会积极和你们分享!再次感谢你们的支持!