nodeJS实现基于Promise爬虫 定时发送信息到指定邮件

英国人Robert Pitt曾在Github上公布了他的爬虫脚本,导致任何人都可以容易地取得Google Plus的大量公开用户的ID信息。至今大概有2亿2千5百万用户ID遭曝光。

亮点在于,这是个nodejs脚本,非常短,包括注释只有71行。

毫无疑问,nodeJS改变了整个前端开发生态。
本文一步步完成了一个基于promise的nodeJS爬虫程序,收集简书任意指定作者的文章信息。并最终把爬下来结果以邮件的形式,自动发给目标对象。千万不要被nodeJS的外表吓到,即使你是初入前端的小菜鸟,或是刚接触nodeJS不久的新同学,都不妨碍对这篇文章的阅读和理解。

爬虫的所有代码可以在我的Github仓库找到,日后这个爬虫程序还会进行不断升级和更新,欢迎关注。

nodeJS VS Python实现爬虫

我们先从爬虫说起。对比一下,讨论为什么nodeJS适合/不适合作为爬虫编写语言。
首先,总结一下:

NodeJS单线程、事件驱动的特性可以在单台机器上实现极大的吞吐量,非常适合写网络爬虫这种资源密集型的程序。

但是,对于一些复杂场景,需要更加全面的考虑。以下内容总结自知乎相关问题,感谢@知乎网友,对答案的贡献。

  • 如果是定向爬取几个页面,做一些简单的页面解析,爬取效率不是核心要求,那么用什么语言差异不大。

  • 如果是定向爬取,且主要目标是解析js动态生成的内容 :
    此时,页面内容是由js/ajax动态生成的,用普通的请求页面+解析的方法就不管用了,需要借助一个类似firefox、chrome浏览器的js引擎来对页面的js代码做动态解析。

  • 如果爬虫是涉及大规模网站爬取,效率、扩展性、可维护性等是必须考虑的因素时候:

  1. PHP:对多线程、异步支持较差,不建议采用。
  2. NodeJS:对一些垂直网站爬取倒可以。但由于分布式爬取、消息通讯等支持较弱,根据自己情况判断。
  3. Python:建议,对以上问题都有较好支持。

当然,我们今天所实现的是一个简易爬虫,不会对目标网站带来任何压力,也不会对个人隐私造成不好影响。毕竟,他的目的只是熟悉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("获取信息出错!");
});

因为我发现,简书中每一篇文章的链接形式如下:
完整形式:“http://www.jianshu.com/p/ab2741f78858”,
即 “http://www.jianshu.com/p/” + “文章id”。

所以,上述代码中相关作者的每篇文章url:由baseUrl和相关文章id拼接组成:

articleIds.forEach(function(item) {
    url = baseUrl + item;
});

articleIds自然是存储作者每篇文章id的数组。

最终,我们把每篇文章的html内容存储在html这个变量中。

异步promise封装

由于作者可能存在多篇文章,所以对于每篇文章的获取和解析我们应该异步进行。这里我使用了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方法做了什么。

html解析

其实很明显,如果您理解了上文的话。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!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容