英国人Robert Pitt曾在Github上公布了他的爬虫脚本,致使任何人均可以容易地取得Google Plus的大量公开用户的ID信息。至今大概有2亿2千5百万用户ID遭曝光。javascript
亮点在于,这是个nodejs脚本,很是短,包括注释只有71行。html
毫无疑问,nodeJS改变了整个前端开发生态。
本文一步步完成了一个基于promise的nodeJS爬虫程序,收集简书任意指定做者的文章信息。并最终把爬下来结果以邮件的形式,自动发给目标对象。千万不要被nodeJS的外表吓到,即便你是初入前端的小菜鸟,或是刚接触nodeJS不久的新同窗,都不妨碍对这篇文章的阅读和理解。前端
爬虫的全部代码能够在个人Github仓库找到,往后这个爬虫程序还会进行不断升级和更新,欢迎关注。java
咱们先从爬虫提及。对比一下,讨论为何nodeJS适合/不适合做为爬虫编写语言。
首先,总结一下:node
NodeJS单线程、事件驱动的特性能够在单台机器上实现极大的吞吐量,很是适合写网络爬虫这种资源密集型的程序。python
可是,对于一些复杂场景,须要更加全面的考虑。如下内容总结自知乎相关问题,感谢@知乎网友,对答案的贡献。git
若是是定向爬取几个页面,作一些简单的页面解析,爬取效率不是核心要求,那么用什么语言差别不大。 github
若是是定向爬取,且主要目标是解析js动态生成的内容 :
此时,页面内容是由js/ajax动态生成的,用普通的请求页面+解析的方法就无论用了,须要借助一个相似firefox、chrome浏览器的js引擎来对页面的js代码作动态解析。ajax
若是爬虫是涉及大规模网站爬取,效率、扩展性、可维护性等是必须考虑的因素时候:
1) PHP:对多线程、异步支持较差,不建议采用。
2) NodeJS:对一些垂直网站爬取倒能够。但因为分布式爬取、消息通信等支持较弱,根据本身状况判断。
3) Python:建议,对以上问题都有较好支持。chrome
固然,咱们今天所实现的是一个简易爬虫,不会对目标网站带来任何压力,也不会对我的隐私形成很差影响。毕竟,他的目的只是熟悉nodeJS环境。适用于新人入门和练手。
一样,任何恶意的爬虫性质是恶劣的,咱们应当全力避免影响,共同维护网络环境的健康。
今天要编写的爬虫目的是爬取简书做者:LucasHC(我本人)在简书平台上,发布过的全部文章信息,包括每篇文章的:
最终爬取结果的输出以下:
同时,以上结果,咱们须要经过脚本,自动发送邮件到指定邮箱。收件内容以下:
所有操做只须要一键即可完成。
咱们的程序一共依赖三个模块/类库:
const http = require("http");
const Promise = require("promise");
const cheerio = require("cheerio");复制代码
http是nodeJS的原生模块,自身就能够用来构建服务器,并且http模块是由C++实现的,性能可靠。
咱们使用Get,来请求简书做者相关文章的对应页面:
http.get(url, function(res) {
var html = "";
res.on("data", function(data) {
html += data;
});
res.on("end", function() {
...
});
}).on("error", function(e) {
reject(e);
console.log("获取信息出错!");
});复制代码
由于我发现,简书中每一篇文章的连接形式以下:
完整形式:“www.jianshu.com/p/ab2741f78…
即 “www.jianshu.com/p/” + “文章id”。
因此,上述代码中相关做者的每篇文章url:由baseUrl和相关文章id拼接组成:
articleIds.forEach(function(item) {
url = baseUrl + item;
});复制代码
articleIds天然是存储做者每篇文章id的数组。
最终,咱们把每篇文章的html内容存储在html这个变量中。
因为做者可能存在多篇文章,因此对于每篇文章的获取和解析咱们应该异步进行。这里我使用了promise封装上述代码:
function getPageAsync (url) {
return new Promise(function(resolve, reject){
http.get(url, function(res) {
...
}).on("error", function(e) {
reject(e);
console.log("获取信息出错!");
});
});
};复制代码
这样一来,好比我写过14篇原创文章。那么对每一片文章的请求和处理全都是一个promise对象。咱们存储在预先定义好的数组当中:
const articlePromiseArray = [];复制代码
接下来,我使用了Promise.all方法进行处理。
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
该方法接受一个promise实例数组做为参数,实例数组中全部实例的状态都变成Resolved,Promise.all返回的实例才会变成Resolved,并将Promise实例数组的全部返回值组成一个数组,传递给回调函数。
也就是说,个人14篇文章的请求对应14个promise实例,这些实例都请求完毕后,执行如下逻辑:
Promise.all(articlePromiseArray).then(function onFulfilled (pages) {
pages.forEach(function(html) {
let info = filterArticles(html);
printInfo(info);
});
}, function onRejected (e) {
console.log(e);
});复制代码
他的目的在于:对每个返回值(这个返回值为单篇文章的html内容),进行filterArticles方法处理。处理所得结果进行printInfo方法输出。
接下来,咱们看看filterArticles方法作了什么。
其实很明显,若是您理解了上文的话。filterArticles方法就是对单篇文章的html内容进行有价值的信息提取。这里有价值的信息包括:
1)文章标题;
2)文章发表时间;
3)文章字数;
4)文章浏览量;
5)文章评论数;
6)文章赞扬数。
function filterArticles (html) {
let $ = cheerio.load(html);
let title = $(".article .title").text();
let publishTime = $('.publish-time').text();
let textNum = $('.wordage').text().split(' ')[1];
let views = $('.views-count').text().split('阅读')[1];
let commentsNum = $('.comments-count').text();
let likeNum = $('.likes-count').text();
let articleData = {
title: title,
publishTime: publishTime,
textNum: textNum
views: views,
commentsNum: commentsNum,
likeNum: likeNum
};
return articleData;
};复制代码
你也许会奇怪,为何我能使用相似jQuery中的$对html信息进行操做。其实这归功于cheerio类库。
filterArticles方法返回了每篇文章咱们感兴趣的内容。这些内容存储在articleData对象当中,最终由printInfo进行输出。
到此,爬虫的设计与实现到了一段落。接下来,就是把咱们爬取的内容以邮件方式进行发送。
这里我使用了nodemailer模块进行发送邮件。相关逻辑放在Promise.all当中:
Promise.all(articlePromiseArray).then(function onFulfilled (pages) {
let mailContent = '';
var transporter = nodemailer.createTransport({
host : 'smtp.sina.com',
secureConnection: true, // 使用SSL方式(安全方式,防止被窃取信息)
auth : {
user : '**@sina.com',
pass : ***
},
});
var mailOptions = {
// ...
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
}
else {
console.log('Message sent: ' + info.response);
}
});
}, function onRejected (e) {
console.log(e);
});复制代码
邮件服务的相关配置内容我已经进行了适当隐藏。读者能够自行配置。
本文,咱们一步一步实现了一个爬虫程序。涉及到的知识点主要有:nodeJS基本模块用法、promise概念等。若是拓展下去,咱们还能够作nodeJS链接数据库,把爬取内容存在数据库当中。固然也可使用node-schedule进行定时脚本控制。固然,目前这个爬虫目的在于入门,实现还相对简易,目标源并非大型数据。
所有内容只涉及nodeJS的冰山一角,但愿你们一块儿探索。若是你对完整代码感兴趣,请点击这里。
Happy Coding!