我想用 request 做一个类似爬虫程序,需要读一个 urllist (用 linebyline 按行读取),然后请求这些 url ,其中有可能卡住,一直没有返回,所以我设置了 timeout 为 3 秒,并加了重试(我知道有 request-retry ,但它用的也是 request )。
整个程序执行过程应该是这样
1. 读取每行
2. 并开始请求
3. 读取下一行,跳到 1
但我发现在读取行的时候,请求并没有发出去,而是占用了 timeout 的时间,证据就是,当 10 个请求时,很快就全处理完了,当 100 个请求时,有几个请求超时,当 500 个时,大部分都超时了,假如一个请求花 0.1 秒,那执行完 30 个请求后, request 就直接超时了。
矛盾来了,我必须设置超时,如果小了,太多的超时,如果大了,浪费时间。请问这种应该怎么改呢?我只想安安静静地发个请求啊
以下是一个 demo 代码
var request = require('request');
var readline = require('linebyline');
rl = readline(process.stdin);
var res_arr = [];
function get_url(url){
return new Promise(function(resolve, reject) {
var l_options = {'url':url};
l_options.timeout = 3 * 1000;
var st_time = Date.now();
console.error('start request');
request(l_options, function(error, response, body) {
console.error('cost '+(Date.now()-st_time)/1000);
if (error) {
console.error(' ' +l_options.url + ' ' + error);
return ;
}
resolve(body);
});
});
}
rl.on('line', function(line, lineCount, byteCount){
var k = lineCount - 1;
get_url(line).then(function(str){
console.log("body "+str.length);
},
function(str){
console.error("failed "+str);
});
});
像这样执行以上代码cat url.txt | node test.js
,会发现先是输出了一坨"start request"(中间没有任何其他输出),然后开始打印请求的时间,而且可以看出时间越来越大(但不是单调递增的,这点很怪)
1
breeswish 2015-09-29 20:27:54 +08:00 1
使用 async 库:
var queue = async.queue(function (url, callback) { request({url: url, timeout: 3000}, function (err, res, body) { callback(); // 告诉 async 任务完成 }); }, 30); // 并发 30 rl.on('line', function (line) { queue.push(line); }); |
2
breeswish 2015-09-29 20:32:56 +08:00
如果希望一次只请求一个,将 30 改成 1 即可
你的预期是一次发起一个请求? 1. 读取第一行 2. 开始请求第一行 3. 当请求完毕后,读取第二行 4. 开始请求第二行 ... 然而实际情况是同时发起了 n 个请求: 1. 读取第一行,开始请求第一行 2. 读取第二行,开始请求第二行 3. 读取第三行,开始请求第三行 ... 100. 读取第 100 行,开始请求第 100 行 101. 第 x 行的请求返回 102. 第 y 行的请求返回 .... |
3
gzlock 2015-09-29 21:40:30 +08:00
@breeswish 现在有采集需求,当采集过程中发现新链接,要提交给任务队列
我用 child_process 实现了任务队列和进程池,跟 async 相比,该用哪个呢? |
5
gzlock 2015-09-29 22:42:53 +08:00
@breeswish 我做的任务队列也是动态的, child_process 可以通过 process.send({type:'newMission',url:'aaaa.com/?a=2'})发送新任务给主进程,由主进程添加到任务队列
|
7
breeswish 2015-09-29 23:47:07 +08:00
@gzlock 如果你已经造好轮子的话么你自己决定咯…反正 async 做流程控制是现成的库, async 可以充分发挥 Node.js 异步并发特性。你这么玩是传统的单线程思路,问题不大,没发挥 Node.js 优势而已
|
10
ysmood 2015-09-30 03:05:53 +08:00
我之前写爬虫都是用 Promise 控制流,比 async 要灵活多了,配合 ES7 的 async-await 语法直接甩 async 一条街。可以试试这个库 https://github.com/ysmood/yaku#asynclimit-list-saveresults-progress
|
11
ysmood 2015-09-30 03:10:15 +08:00
https://github.com/ysmood/nokit/blob/master/examples/threadPool.coffee 这是我写的一个使用上面提到函数的示例,典型的 producer broker consumer 模型,会无穷无尽的爬下去。
|
12
magicyu1986 2015-09-30 09:23:21 +08:00
最好用一个信号量来控制请求速度,不然瞬间发一大堆请求,失败率肯定会增高.
|
13
morefreeze OP @breeswish 感谢提出的 async 的库
我在用的时候,发现在 rl.on('line')时, push 到 queue 里,但我如果在最前面定义 queue.drain 却并没有出现完成的情况 这是为什么? |
14
breeswish 2015-09-30 13:18:12 +08:00
@morefreeze 当队列空且任务完成后才会触发 drain. 你看看是不是没有调用 callback()
例如对于以下代码: https://gist.github.com/SummerWish/da6d5980737a411f4e3d 应当在 4500ms 后输出 drain : 500ms: 添加了两个任务(并发是 1 ) 2500ms: 第一个任务完成,开始第二个任务 4500ms: 第二个任务完成, drain |
15
morefreeze OP @breeswish 是因为我传了 callback 加了参数。
所以这个 callback 是有什么用呢,如果无法加参数的话 |
16
breeswish 2015-09-30 14:26:50 +08:00
@morefreeze 第一个参数是 err ;对于 async 其他某些模型比如 waterfall 等还可以传第二个参数作为 data 。
> worker(task, callback) - An asynchronous function for processing a queued task, which must call its callback(err) argument when finished, with an optional error as an argument. If you want to handle errors from an individual task, pass a callback to q.push(). `push` 本身可以接受一个任务完成的回调 不过 `err` 应该是不影响整体流程的.. |
17
morefreeze OP @breeswish 我找到原因了,因为我有句判断如果出错就直接 return 没有调用 callback ,所以没触发 drain ,另外 callback 参数如你所说,是可以正常处理的
多谢指导 |