Nodejs第四课

使用eventproxy控制并发

目标

输出CNode社区社区首页的所有主题的标题,链接和第一条评论,以 json 的格式。原文地址:lesson4

示例:

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"title": "【公告】发招聘帖的同学留意一下这里",
"href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12",
"comment1": "呵呵呵呵"
},
{
"title": "发布一款 Sublime Text 下的 JavaScript 语法高亮插件",
"href": "http://cnodejs.org/topic/54207e2efffeb6de3d61f68f",
"comment1": "沙发!"
}
]

挑战

输出帖子的作者,以及他在cnode社区的积分值,这里和原文不同,作者看错了,不过也算挑战
示例:

1
2
3
4
5
6
7
8
9
10
[
{
"title": "【公告】发招聘帖的同学留意一下这里",
"href": "http://cnodejs.org/topic/541ed2d05e28155f24676a12",
"comment1": "呵呵呵呵",
"author1": "auser",
"score1": 80
},
...
]

原文: 以上文目标为基础,输出 comment1 的作者,以及他在 cnode 社区的积分值。
就一个地方不同,我过后会修改代码附上

首先app.js这样写,获取文章的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
var eventproxy = require('eventproxy');
var superagent = require('superagent');
var cheerio = require('cheerio');
// url 模块是 Node.js 标准库里面的
// http://nodejs.org/api/url.html
var url = require('url');
var cnodeUrl = 'https://cnodejs.org/';
superagent.get(cnodeUrl)
.end(function (err, res) {
if (err) {
return console.error(err);
}
var topicUrls = [];
var $ = cheerio.load(res.text);
// 获取首页所有的链接
$('#topic_list .topic_title').each(function (idx, element) {
var $element = $(element);
// $element.attr('href') 本来的样子是 /topic/542acd7d5d28233425538b04
// 我们用 url.resolve 来自动推断出完整 url,变成
// https://cnodejs.org/topic/542acd7d5d28233425538b04 的形式
// 具体请看 http://nodejs.org/api/url.html#url_url_resolve_from_to 的示例
var href = url.resolve(cnodeUrl, $element.attr('href'));
topicUrls.push(href);
});
console.log(topicUrls);
});

Copy:(这段是在太多,偷师学艺)
用 js 写过异步的同学应该都知道,如果你要并发异步获取两三个地址的数据,并且要在获取到数据之后,对这些数据一起进行利用的话,常规的写法是自己维护一个计数器。

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

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

假设我们不使用 eventproxy 也不使用计数器时,抓取三个源的写法是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
// 参考 jquery 的 $.get 的方法
$.get("http://data1_source", function (data1) {
// something
$.get("http://data2_source", function (data2) {
// something
$.get("http://data3_source", function (data3) {
// something
var html = fuck(data1, data2, data3);
render(html);
});
});
});

使用计数器:

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
(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();
});
$.get('http://data3_source', function (data) {
result.data3 = data;
count++;
handle();
});
function handle() {
if (count === 3) {
var html = fuck(result.data1, result.data2, result.data3);
render(html);
}
}
})();

用 eventproxy,写出来是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var ep = new eventproxy();
ep.all('data1_event', 'data2_event', 'data3_event', function (data1, data2, data3) {
var html = fuck(data1, data2, data3);
render(html);
});
$.get('http://data1_source', function (data) {
ep.emit('data1_event', data);
});
$.get('http://data2_source', function (data) {
ep.emit('data2_event', data);
});
$.get('http://data3_source', function (data) {
ep.emit('data3_event', data);
});

大家自行学习一下这个 API 吧:[eventproxy])https://github.com/JacksonTian/eventproxy#%E9%87%8D%E5%A4%8D%E5%BC%82%E6%AD%A5%E5%8D%8F%E4%BD%9C)

目标完成代码:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var eventproxy = require('eventproxy');
var superagent = require('superagent');
var cheerio = require('cheerio');
//加载标准库模块url
var url = require('url');
var cnodeUrl = 'https://cnodejs.org/';
superagent.get(cnodeUrl)
.end(function(err, res){
if (err) {
return console.error(err);
};
var $ = cheerio.load(res.text);
var topicUrls = [];
//获取链接
$('#topic_list .topic_title').each(function(idx, element){
var $element = $(element);
var href = url.resolve(cnodeUrl, $element.attr('href'));
topicUrls.push(href);
});
// console.log(topicUrls);
// 创建对象实例
var ep = new eventproxy();
//监听topicUrls.length次'topic_html'再行动
ep.after('topic_html', topicUrls.length, function(topics){
//topic是个数组,包含了40次ep.emit('topic_html', pair)中的那40个pair
topics = topics.map(function(topicPair){
var topicUrl = topicPair[0];
var topicHtml = topicPair[1];
var $ = cheerio.load(topicHtml);
return ({
title: $('.topic_full_title').text().trim(),
href: topicUrl,
comment: $('.reply_content').eq(0).text().trim()
});
});
console.log('final:');
console.log(topics);
});
topicUrls.forEach(function(topicUrl){
superagent.get(topicUrl)
.end(function(err, sres){
console.log('fetch: ' + topicUrl + ' successful');
ep.emit('topic_html', [topicUrl, sres.text]);
});
});
});

目标代码看似没问题,可是服务器异常(服务器受不了),我采用了递归完成了挑战,估计服务器更受不了,因为如果访问有错误,我就采用递归又去访问,直到40次全部完成

因为看错了,所以这是自己写的挑战自我的代码:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
var eventproxy = require('eventproxy');
var superagent = require('superagent');
var cheerio = require('cheerio');
//加载标准库模块url
var url = require('url');
var cnodeUrl = 'https://cnodejs.org/';
superagent.get(cnodeUrl)
.end(function(err, res){
if (err) {
return console.error(err);
};
var $ = cheerio.load(res.text);
//一级,得到所有主题
var topicUrls = [];
//获取链接
$('#topic_list .topic_title').each(function(idx, element){
var $element = $(element);
var href = url.resolve(cnodeUrl, $element.attr('href'));
topicUrls.push(href);
});
// 创建对象实例
var ep = new eventproxy();
//监听topicUrls.length次'topic_html',再执行
ep.after('topic_html', topicUrls.length, function(topics){
//topic是个数组,包含了40次ep.emit('topic_html', pair)中的那40个pair
// array.map方法会遍历数组并且每遍历一次执行一次callback
// callback执行后的返回值组合起来又形成一个新的数组
/*
topics = topics.map(function(topicPair){
var topicUrl = topicPair[0];
var topicHtml = topicPair[1];
console.log(topicHtml);
var $ = cheerio.load(topicHtml);
return ({
title: $('.topic_full_title').text().trim(),
href: topicUrl,
comment: $('.reply_content').eq(0).text().trim()
});
});
console.log('final:');
console.log(topics);
*/
ep.after('user', topics.length, function(users){
var finalInfors = users.map(function(user){
var preInfo = user[0];
var userHtml = user[1];
var $ = cheerio.load(userHtml);
var authorScore = $('.userinfo .user_profile .big').text();
return ({
title: preInfo[0],
href: preInfo[1],
comment: preInfo[2],
author: preInfo[3],
score: authorScore
});
});
console.log('final:');
console.log(finalInfors);
});
//这里如果有错误,就使用递归
//请不要多次测试,服务器负载受不了
//正式因为服务器负载问题,有时候访问会出错,所以使用了递归
var getScore = function(userUrl, topicInfor){
superagent.get(userUrl)
.end(function(err, ssres){
if (err) {
getScore(userUrl, topicInfor);
return;
}
console.log('fetch suburl:' + userUrl + " success");
ep.emit(
'user',
[
topicInfor,
ssres.text
]
);
});
}
topics.forEach(function(topic){
var topicUrl = topic[0];
var topicHtml = topic[1];
var $ = cheerio.load(topicHtml);
var $userInfo = $('.user_card .user_name a');
getScore(
url.resolve(cnodeUrl, $userInfo.attr('href')),
[
$('.topic_full_title').text().trim(),
topicUrl,
$('.reply_content').eq(0).text().trim(),
$userInfo.text()
]
);
});
});
//这里如果有错误,就使用递归
//请不要多次测试,服务器负载受不了
//正式因为服务器负载问题,有时候访问会出错,所以使用了递归
var getText = function(topicUrl){
superagent.get(topicUrl)
.end(function(err, sres){
if (err) {
getText(topicUrl);
return;
}
console.log('fetch: ' + topicUrl + " success");
ep.emit(
'topic_html',
[
topicUrl,
sres.text
]
);
});
}
topicUrls.forEach(function(topicUrl){
getText(topicUrl);
});
});

原文中挑战目标的代码:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
var eventproxy = require('eventproxy');
var superagent = require('superagent');
var cheerio = require('cheerio');
//加载标准库模块url
var url = require('url');
var cnodeUrl = 'https://cnodejs.org/';
superagent.get(cnodeUrl)
.end(function(err, res){
if (err) {
return console.error(err);
};
var $ = cheerio.load(res.text);
//一级,得到所有主题
var topicUrls = [];
//获取链接
$('#topic_list .topic_title').each(function(idx, element){
var $element = $(element);
var href = url.resolve(cnodeUrl, $element.attr('href'));
topicUrls.push(href);
});
// 创建对象实例
var ep = new eventproxy();
ep.after('topic_html', topicUrls.length, function(topics){
ep.after('user', topics.length, function(users){
var finalInfors = users.map(function(user){
var preInfo = user[0];
var userHtml = user[1];
var $ = cheerio.load(userHtml);
var authorScore = $('.userinfo .user_profile .big').text();
return ({
title: preInfo[0],
href: preInfo[1],
comment: preInfo[2],
author: preInfo[3],
score: authorScore
});
});
console.log('final:');
console.log(finalInfors);
});
//这里如果有错误,就使用递归
//请不要多次测试,服务器负载受不了
//正式因为服务器负载问题,有时候访问会出错,所以使用了递归
var getScore = function(userUrl, topicInfor){
superagent.get(userUrl)
.end(function(err, ssres){
if (err) {
getScore(userUrl, topicInfor);
return;
}
console.log('fetch suburl:' + userUrl + " success");
ep.emit(
'user',
[
topicInfor,
ssres.text
]
);
});
}
topics.forEach(function(topic){
var topicUrl = topic[0];
var topicHtml = topic[1];
var $ = cheerio.load(topicHtml);
var $authorInfor = $('.author_content').eq(0).find(".reply_author");
getScore(
url.resolve(cnodeUrl, $authorInfor.attr('href')),
[
$('.topic_full_title').text().trim(),
topicUrl,
$('.reply_content').eq(0).text().trim(),
$authorInfor.text().trim()
]
);
});
});
//这里如果有错误,就使用递归
//请不要多次测试,服务器负载受不了
//正式因为服务器负载问题,有时候访问会出错,所以使用了递归
var getText = function(topicUrl){
superagent.get(topicUrl)
.end(function(err, sres){
if (err) {
getText(topicUrl);
return;
}
console.log('fetch: ' + topicUrl + " success");
ep.emit(
'topic_html',
[
topicUrl,
sres.text
]
);
});
}
topicUrls.forEach(function(topicUrl){
getText(topicUrl);
});
});

前两段代码亲测可用,第三段代码只是修改了获取作者积分的时候传递的url和作者的名称
也许你会问为什么没有测试,因为社区发帖子的人很多啊,有一些帖子是没有人回复的,获取不到评论
测试的时候还以为代码有错了,555

# lesson
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×