Node爬虫之使用 eventproxy 控制并发

上一篇文章《Node实现简单爬虫》我们介绍了如何使用 superagentcheerio来取主页内容,那只需要发起一次 http get 请求就能办到。但这次,我们需要取出每个主题的第一条评论,这就要求我们对每个主题的链接发起请求,并用 cheerio 去取出其中的第一条评论。

eventproxy 模块

在这之前,先介绍一下eventproxy模块,假设我们不使用 eventproxy 也不使用计数器时,抓取三个源的写法是这样的:

$.get("http://data1_source", function (data1) {
		  // something
  $.get("http://data2_source", function (data2) {
		    // something 
    $.get("http://data3_source", function (data3) {
		      // something
    });
  });
});

但是如果是 10 次请求呢?那就要嵌套 10 个回调了,这样写不仅代码太乱,而且这样写效率太低,跟同步获取是一样的!
但如果我们用计数器来写,可以这样写:

(function () {
  var count = 0;
  var result = {};

  $.get('http://data1_source', function (data) {
    result.data1 = data;
    count++;
    handle();
    });
  $.get('http://data2_source', function (data) {
    result.data2 = data;
    count++;
    handle();
    });

  function handle() {
    if (count === 2) {
       // do something
    }
  }
})();

上面的代码是先定义一个 var count = 0,然后每次抓取成功以后,就count++。如果你是要抓取两个源的数据,由于你根本不知道这些异步操作到底谁先完成,那么每次当抓取成功的时候,就判断一下 count === 2。当值为真时,使用另一个函数继续完成操作。

eventproxy 模块就起到了这个计数器的作用,它来帮你管理到底这些异步操作是否完成,完成之后,它会自动调用你提供的处理函数,并将抓取到的数据当参数传过来。
如果用 eventproxy 模块,可以这样写:

var ep = new eventproxy();
ep.all('data1_event', 'data2_event', , function (data1, data2) {
  // do something
});

$.get('http://data1_source', function (data) {
  ep.emit('data1_event', data);
  });

$.get('http://data2_source', function (data) {
  ep.emit('data2_event', data);
  });

通过 ep.all('data1_event', 'data2_event', function (data1, data2) {}); 监听了两个事件,分别是 data1_event , data2_event,每次当一个源的数据抓取完成时,就通过 ep.emit() 来告诉 ep 自己,某某事件已经完成了。
当三个事件未同时完成时,ep.emit()调用之后不会做任何事;当三个事件都完成的时候,就会调用末尾的那个回调函数,来对它们进行统一处理。

实践

1、首先获取首页所有URL的地址

	superagent.get(cnodeUrl)
		.end(function(err, res) {
			if(err) {
				return console.error(err);
			}

			var topicUrls = [];
			var $ = cheerio.load(res.text);

			$('.clearfix .list_con  .title h2 a').each(function(idx, element) {
				var $element = $(element);
				// console.log('$element',$element);

				var href = url.resolve(cnodeUrl,$element.attr('href'));
				topicUrls.push(href);
			});

			console.log('topicUrls', topicUrls);
})

2、接下来,我们把这些地址都抓取一遍,就完成了

		 // 得到一个 eventproxy 的实例
           var ep = new eventproxy();

        //  命令 ep 重复监听 topicUrls.length 次(在这里也就是 21 次) 'topic_html' 事件再行动
			ep.after('topic_html', topicUrls.length, function(topics) {
				topics = topics.map(function(topicPair) {
					var topicUrl = topicPair[0];
					var topicHtml = topicPair[1];
					var $ = cheerio.load(topicHtml);

					return ({
						title: $('.title-article').text().trim(),
						href: topicUrl,
						comment1: $('.comment').eq(0).text().trim()
					})
				})

				console.log('final:', topics);
				response.send(topics);
			})

			topicUrls.forEach(function(topicUrl) {
				superagent.get(topicUrl)
					.end(function(err, res) {
						console.log('fetch ' + topicUrl + ' successful.');
						ep.emit('topic_html', [topicUrl, res.text])
					})
			})

完整代码:


var eventproxy = require('eventproxy');
var superagent = require('superagent');
var cheerio = require('cheerio');
var express = require('express');
var url = require('url');

var cnodeUrl = 'https://blog.csdn.net/';

var app = express();

app.get('/', function(request, response, next) {
	superagent.get(cnodeUrl)
		.end(function(err, res) {
			if(err) {
				return console.error(err);
			}

			var topicUrls = [];
			var $ = cheerio.load(res.text);

			$('.clearfix .list_con  .title h2 a').each(function(idx, element) {
				var $element = $(element);
				// console.log('$element',$element);

				var href = url.resolve(cnodeUrl,$element.attr('href'));
				topicUrls.push(href);
			});

			console.log('topicUrls', topicUrls);

			var ep = new eventproxy();

			ep.after('topic_html', topicUrls.length, function(topics) {
				topics = topics.map(function(topicPair) {
					var topicUrl = topicPair[0];
					var topicHtml = topicPair[1];
					var $ = cheerio.load(topicHtml);

					return ({
						title: $('.title-article').text().trim(),
						href: topicUrl,
						comment1: $('.comment').eq(0).text().trim()
					})
				})

				console.log('final:', topics);
				response.send(topics);
			})

			topicUrls.forEach(function(topicUrl) {
				superagent.get(topicUrl)
					.end(function(err, res) {
						console.log('fetch ' + topicUrl + ' successful.');
						ep.emit('topic_html', [topicUrl, res.text])
					})
			})

		})

})


app.listen(3000, function () {
    console.log('app is listenling at port 3000');
});

结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_32682137/article/details/82768554