起步软件技术论坛
搜索
 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 6764|回复: 7

[分享] 用NodeJs做一个小爬虫,附源码!

  [复制链接]

13

主题

28

帖子

388

积分

中级会员

Rank: 3Rank: 3

积分
388
QQ
发表于 2016-8-3 13:50:54 | 显示全部楼层 |阅读模式

前言
    利用爬虫可以做很多事情,单身汉子们可以用爬虫来收集各种妹子情报,撩妹族们可以用爬虫收集妹子想要的小东西,赚大钱的人可以用来分析微博言论与股票涨跌的关系诸如此类的,简直要上天了。
    你们感受一下 点我点我:
   
   1.jpg
   2.jpg

蠢蠢欲动
    抛开机器学习这种貌似很高大上的数据处理技术,单纯的做一个爬虫获取数据还是非常简单的。对于前段er们来说,生在有nodejs的年代真是不要太幸福了,下面就用nodejs来做一个爬虫吧。
    这次我们先拿CSDN来练练手,爬妹子什么的,其实大同小异。要实现的功能就是爬取前端板块的博文,输出作者的信息。
    首先是工具的准备,nodejs当然是必须要装好的,我这里用的是目前最新版的6.2.0版本。另外由于我们需要通过各种http请求去爬取内容,所以一个好用的http调试工具也是必需的,这里我推荐是用postman。
搭建环境
根据爬虫需要的功能确定我们要使用的第三方库:
   后端服务: express
   发出http请求: superagent
   控制并发请求:async + eventproxy
   分析网页内容:cheerio
相关库的 API 请到对应的 GitHub 项目页上去了解,这里我就不再赘述了,对应的 package.json :

  1. {
  2.   "name": "spider",
  3.   "version": "0.0.0",
  4.   "description": "learn nodejs on github",
  5.   "scripts": {
  6.     "start": "node app.js"
  7.   },
  8.   "dependencies": {
  9.     "async": "^2.0.0-rc.6",
  10.     "cheerio": "^0.20.0",
  11.     "eventproxy": "^0.3.4",
  12.     "express": "^4.9.5",
  13.     "superagent": "^2.0.0"
  14.   },
  15. }
复制代码

写好的 package 记得安装下,这样我们就搭好了开发环境。
爬虫主体
下面按照爬虫需要的功能来一步步写爬虫主体。

后台服务部分
实现的功能是接收前端请求启动爬虫,完成信息爬取之后将信息返回给前端。后台服务部分我这里使用了 express 框架,这里比较简单也可以使用原生的 http 模块。简单框架如下:

  1. <P>var express = require('express');
  2. var app = express();
  3. app.get('/', function (req, res, next) {
  4.     // your code here
  5. });
  6. app.listen(3000, function (req, res) {
  7.     console.log('app is running at port 3000');
  8. });</P>
复制代码

在 get 处理中插入我们的响应代码,包括启动爬虫,结果信息输出等。

文章链接的爬取
这里我们用到的是 superagent 这个库来实现,此库作者是个多产大神,我们这里用到的库基本都是他写的,颤抖吧、少年们!

  1. <P>superagent.get(Url).end(function (err, res) {
  2.     if (err) { return next(err); }
  3.     // your code here
  4. });</P>
复制代码

Url 为我们请求的地址,使用 get 的方式请求,其实效果跟你用浏览器打开 Url 的效果是一样的,返回来的数据都放在 res 中,对 res 分析就可以得到我们想要的数据了。

数据的处理
这里我们用到的是 cheerio 这个库,他可以让我们以 jQuery 的方式操作返回的数据,实在太贴心了。

  1. <P>// 提取作者博客链接,注意去重
  2. var $ = cheerio.load(sres.text);
  3. $('.blog_list').each(function (i, e) {
  4.     var u = $('.user_name', e).attr('href');
  5.     if (authorUrls.indexOf(u) === -1) {
  6.         authorUrls.push(u);
  7.     }
  8. });</P>
复制代码

是不是很熟悉的感觉? 完全就是 jQuery 的语法呀。

文章作者信息的爬取
这里我们要进入作者主页去爬取相应的消息,跟上一步爬取链接是一样的。
  1. superagent.get(authorUrl)
  2.     .end(function (err, ssres) {
  3.         if (err) { callback(err, authorUrl + ' error happened!'); }
  4.         var $ = cheerio.load(ssres.text);
  5.         var result = {
  6.             userId: url.parse(myurl).pathname.substring(1),
  7.             blogTitle: $("#blog_title a").text(),
  8.             visitCount: parseInt($('#blog_rank>li').eq(0).text().split(/[::]/)[1]),
  9.             score: parseInt($('#blog_rank>li').eq(1).text().split(/[::]/)[1]),
  10.             oriCount: parseInt($('#blog_statistics>li').eq(0).text().split(/[::]/)[1]),
  11.             copyCount: parseInt($('#blog_statistics>li').eq(1).text().split(/[::]/)[1]),
  12.             trsCount: parseInt($('#blog_statistics>li').eq(2).text().split(/[::]/)[1]),
  13.             cmtCount: parseInt($('#blog_statistics>li').eq(3).text().split(/[::]/)[1])
  14.         };
  15.         callback(null, result);
  16.     });
复制代码
这里我们是用 callback 返回结果的。

并发的控制
因为我们的请求都是异步的,所以需要执行成功的回调函数中执行下一步操作,在多并发的情况下,就需要一个计数器来判断是否所有并发均已成功执行完。这里用到的是 eventproxy 这个库来替我们管理并发结果。
CSDN上web前端有3页,所以我们要执行 3 次爬取文章链接,用 eventproxy 的写法就是:
  1. var baseUrl = 'http://blog.csdn.net/web/index.html';
  2. var pageUrls = [];
  3. for (var _i = 1; _i < 4; _i++) {
  4.     pageUrls.push(baseUrl + '?&page=' + _i);
  5. }
  6. ep.after('get_topic_html', pageUrls.length, function (eps) {
  7.     // 文章链接都已经爬取完了
  8. });
  9. pageUrls.forEach(function (page) {
  10.     superagent.get(page).end(function (err, sres) {
  11.         // 文章链接的爬取
  12.         ep.emit('get_topic_html', 'get authorUrls successful');
  13.     });
  14. });
复制代码

    简单来说,就是会检测 'get_topic_html' 的emit事件,发生指定次数之后就调用 ep.after 函数。

并发请求数控制
    本来呢,到这里就完了,但是我们爬取作者信息时都是用异步操作的,所以同时可能会有几十甚至几百个请求同时发送给目标网站。出于安全角度考虑,目标网站可能会拒绝我们的请求,所以我们要控制并发数量,这里我们用的是 async 这个库来实现。

  1. <P>// 控制最大并发数为5,在结果中取出callback返回来的整个结果数组。
  2. async.mapLimit(authorUrls, 5, function (myurl, callback) {
  3.     // 请求作者信息
  4. }, function (err, result) {
  5.     console.log('=========== result: ===========\n', result);
  6.     res.send(result);
  7. });</P>
复制代码

这里 authorUrls 是我们前一步爬取好的作者链接数组,async 会根据数组长度依次执行。之前在作者信息爬取部分我们使用了回调函数返回数据,这个也是async 提供的接口。最终数组中所有元素都被执行了一遍之后,就会将 callback 返回的数据放入 result 数组中,将这个数组返回给前端即可。
效果

通过 node app.js 执行后台程序,在 postman 中输入 http://localhost:3000 查看结果:
    3.jpg

可以看到在返回中的body中已经将我们需要的数据返回来了。
至此,我们的小爬虫就完成了,是不是很简单呢?


评分

参与人数 3威望 +30 收起 理由
不羁的风 + 10 看不懂,今天的积分全给你了!!.
tiger0425 + 10 很给力!
biyaooo + 10 很给力!

查看全部评分

13

主题

28

帖子

388

积分

中级会员

Rank: 3Rank: 3

积分
388
QQ
 楼主| 发表于 2016-8-3 13:51:48 | 显示全部楼层
完整的代码

  1. <P>/***
  2. * Created by justeptech on 2016/7/11.
  3. */</P>
  4. <P>var cheerio = require('cheerio');
  5. var superagent = require('superagent');
  6. var async = require('async');
  7. var url = require('url');</P>
  8. <P>var express = require('express');
  9. var app = express();</P>
  10. <P>var eventproxy = require('eventproxy');
  11. var ep = eventproxy();</P>
  12. <P>var baseUrl = 'http://blog.csdn.net/web/index.html';
  13. var pageUrls = [];
  14. for (var _i = 1; _i < 4; _i++) {
  15.     pageUrls.push(baseUrl + '?&page=' + _i);
  16. }</P>
  17. <P>app.get('/', function (req, res, next) {
  18.     var authorUrls = [];
  19.     // 命令 ep 重复监听 emit事件(get_topic_html) 3 次再行动
  20.     ep.after('get_topic_html', pageUrls.length, function (eps) {
  21.         var concurrencyCount = 0;
  22.         // 利用callback函数将结果返回去,然后在结果中取出整个结果数组。
  23.         var fetchUrl = function (myurl, callback) {
  24.             var fetchStart = new Date().getTime();
  25.             concurrencyCount++;
  26.             console.log('现在的并发数是', concurrencyCount, ',正在抓取的是', myurl);
  27.             superagent.get(myurl)
  28.                 .end(function (err, ssres) {
  29.                     if (err) {
  30.                         callback(err, myurl + ' error happened!');
  31.                     }</P>
  32. <P>                    var time = new Date().getTime() - fetchStart;
  33.                     console.log('抓取 ' + myurl + ' 成功', ',耗时' + time + '毫秒');
  34.                     concurrencyCount--;</P>
  35. <P>                    var $ = cheerio.load(ssres.text);
  36.                     var result = {
  37.                         userId: url.parse(myurl).pathname.substring(1),
  38.                         blogTitle: $("#blog_title a").text(),
  39.                         visitCount: parseInt($('#blog_rank>li').eq(0).text().split(/[::]/)[1]),
  40.                         score: parseInt($('#blog_rank>li').eq(1).text().split(/[::]/)[1]),
  41.                         oriCount: parseInt($('#blog_statistics>li').eq(0).text().split(/[::]/)[1]),
  42.                         copyCount: parseInt($('#blog_statistics>li').eq(1).text().split(/[::]/)[1]),
  43.                         trsCount: parseInt($('#blog_statistics>li').eq(2).text().split(/[::]/)[1]),
  44.                         cmtCount: parseInt($('#blog_statistics>li').eq(3).text().split(/[::]/)[1])
  45.                     };
  46.                     callback(null, result);
  47.                 });</P>
  48. <P>        };
  49.         // 控制最大并发数为5,在结果中取出callback返回来的整个结果数组。
  50.         async.mapLimit(authorUrls, 5, function (myurl, callback) {
  51.             fetchUrl(myurl, callback);
  52.         }, function (err, result) {
  53.             console.log('=========== result: ===========\n', result);
  54.             res.send(result);
  55.         });
  56.     });</P>
  57. <P>    // 获取每页的链接数组,这里不要用emit返回了,因为我们获得的已经是一个数组了。
  58.     pageUrls.forEach(function (page) {
  59.         superagent.get(page).end(function (err, sres) {
  60.             // 常规的错误处理
  61.             if (err) {
  62.                 return next(err);
  63.             }
  64.             // 提取作者博客链接,注意去重
  65.             var $ = cheerio.load(sres.text);
  66.             $('.blog_list').each(function (i, e) {
  67.                 var u = $('.user_name', e).attr('href');
  68.                 if (authorUrls.indexOf(u) === -1) {
  69.                     authorUrls.push(u);
  70.                 }
  71.             });
  72.             console.log('get authorUrls successful!\n', authorUrls);
  73.             ep.emit('get_topic_html', 'get authorUrls successful');
  74.         });
  75.     });
  76. });</P>
  77. <P>app.listen(3000, function (req, res) {
  78.     console.log('app is running at port 3000');
  79. });</P>
复制代码
关于Nodejs搭建小爬虫的介绍就到这里了,码字不易,顺手点赞哈!
回复 支持 1 反对 0

使用道具 举报

718

主题

2841

帖子

5657

积分

论坛元老

Rank: 8Rank: 8

积分
5657
QQ
发表于 2016-8-3 13:56:30 | 显示全部楼层
赞赞
WEX5初学者,欢迎初学者交流
QQ:597558229
tel:15857336322
回复

使用道具 举报

33

主题

213

帖子

1158

积分

金牌会员

Rank: 6Rank: 6

积分
1158
QQ
发表于 2016-8-3 13:56:44 | 显示全部楼层
牛人,赞一个
回复 支持 反对

使用道具 举报

7

主题

17

帖子

35

积分

新手上路

Rank: 1

积分
35
QQ
发表于 2016-8-3 13:56:55 | 显示全部楼层
好东西   就是不知道这么用啊
回复 支持 反对

使用道具 举报

73

主题

345

帖子

1695

积分

金牌会员

Rank: 6Rank: 6

积分
1695
QQ
发表于 2016-8-3 13:59:24 | 显示全部楼层
先收藏了,以后研究。高人啊!
回复 支持 反对

使用道具 举报

发表于 2016-8-3 14:18:19 | 显示全部楼层
拜读一下
回复

使用道具 举报

25

主题

192

帖子

715

积分

高级会员

Rank: 4

积分
715
QQ
发表于 2020-5-7 11:27:41 | 显示全部楼层
16年的,可以参考一下!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|X3技术论坛|Justep Inc.    

GMT+8, 2024-11-22 12:57 , Processed in 0.122661 second(s), 28 queries .

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表