node爬虫

一直都对爬虫很感兴趣,能从各种网站爬数据是一件很有趣的事情,然后自己拿数据建站啊什么的(是不是很low。。。),以前爬虫呢要用ruby,python这类后端语言。node.js的出现能够使js也能写爬虫了,由于 nodejs 强大的异步特性,让我们可以轻松以异步高并发去爬取网站。下面就来看一看如何简单的从网站上爬取一些数据。

相关依赖的用法

superagent

superagent是个 http 方面的库,可以发起 get 或 post 请求。
eg:

superagent.get(url)
.end(function(err,res){
    //处理函数
    //res.text包含了网页的html内容
})

更多用法点击superagent查看api

cheerio

cheerio可以理解成一个 Node.js 版的 jquery,使用方法跟jq一样

eg:

1
2
3
4
5
6
7
var cheerio = require('cheerio'),
$ = cheerio.load('<h2 class="title">Hello world</h2>');
$('h2.title').text('Hello there!');
$('h2').addClass('welcome');
$.html();
//=> <h2 class="title welcome">Hello there!</h2>

将html内容传递给cheerio.load()之后,就得到了一个实现了jQuery接口的变量,通常命名为$,其余用法与jQuery类似,详细内容见cheerio

eventproxy

eventproxy 仅仅是一个很轻量的工具,但是能够带来一种事件式编程的思维变化。将串行等待变成并行等待,提升多异步协作场景下的执行效率是它的特点之一。

eventproxy 提供了不少其他场景所需的 API,但最最常用的用法就是以下的这种:

  1. var ep = new eventproxy(); 得到一个 eventproxy 实例。
  2. 再告诉它你要监听哪些事件,并给它一个回调函数。ep.all('event1', 'event2', function (result1, result2) {})
  3. 在适当的时候通过ep.emit('event_name',event_data)

解释:ep.all('event1', 'event2', function (result1, result2) {})监听两个事件,event1,event2;每次当一个源的数据抓取完成时,就通过 ep.emit() 来告诉 ep 自己,某某事件已经完成了。当所有事件都完成了,才会调用all后面的回调函数来对它们进行统一处理。

更多详细内容见api

async

Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript.

Async 是一种实用的模块,提供了强大的功能与异步JavaScript的工作。
官方api在这里https://github.com/caolan/async

详细 async demo演示https://github.com/alsotang/async_demo

文章实例将会用到async的mapLimit(arr, limit, iterator, callback)来控制并发连接数

详细步骤

一.请求首页所有文章的url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app.get('/',function(req,resp,next){
var blogUrl="http://www.w3cfuns.com/";
superagent.get(blogUrl)
.end(function(err,res){
if(err){
return console.error(err);
}
var total_blogUrlS=[];
var $=cheerio.load(res.text);
//得到了一个实现了jQuery接口的变量
//下面获取元素就跟jQuery一样了
$('.noteinfo .media-heading').each(function(index,element){
var $element=$(element);
//我们用 url.resolve 来自动推断出完整 url
var href = url.resolve(blogUrl, $element.find('a').eq(1).attr('href'));
//var title =$element.find('a').eq(1).text();
total_blogUrlS.push(href);
});
//如果在这里sesp.send(total_blogUrlS),地址栏键入localhost:3000将会看到所有的链接。
)};
});

二.根据获取到的url数组再次请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//得到首页20个链接后
// 得到一个 eventproxy 的实例
var ep = new eventproxy();
// 命令 ep 重复监听 total_blogUrlS.length 次 `topic_html` 事件再行动
ep.after('topic_html',total_blogUrlS.length,function(list){
// 在所有文件的异步执行结束后将被执行
//这里的参数list是个数组,包含了20组ep.emit('topic_html',content)中的20个content
var list=list.map(function(every_list){
var topic_Html=every_list[1];
var everyUrl=every_list[0];
var $=cheerio.load(topic_Html);
return({
href: everyUrl,
author: $('#author_name').text().trim(),
author_count1: "总笔记数:"+$('#author_count li').find('i').eq(0).text().trim(),
//author_count2: "总阅读量:"+$('#author_count li').find('i').eq(1).text().trim(),
comment1: $('#f1 .media-body').find('p').text().trim()
})
});
resp.send(list);
});
total_blogUrlS.forEach(function(everyUrl){
superagent.get(everyUrl)
.end(function(err,res){
console.log("fetch:"+everyUrl+".successful");
//// 触发结果事件,每次触发的数据,将会按触发顺序,存为数组作为参数传入handle函数。
ep.emit('topic_html',[everyUrl, res.text]);
});
});

运行结果如图:

会发现有的数据没有被请求下来,原因就是,同时并发请求数多了之后,有的网站会认为是恶意请求,所以很多网站都是有请求限制的。

三. 加上async控制并发数

1
2
3
4
5
6
7
8
9
10
11
12
13
//用async控制并发
async.mapLimit(total_blogUrlS,5,function(everyUrl,callback){
superagent.get(everyUrl)
.end(function(err,res){
console.log("fetch:"+everyUrl+".successful");
ep.emit('topic_html',[everyUrl, res.text]);
callback();
});
},function(err){
console.log(err);
});

async.mapLimit()的用法见上面的详细async demo;
运行截图如下:

做到这里,一个简单的爬虫就算是写好了。以上就是这两天学的node爬虫了,后续会继续了解爬虫相关的知识。