[求助]nodejs 中的 request 设置 timeout 问题

2015-09-29 20:10:26 +08:00
 morefreeze

request 文档

我想用 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"(中间没有任何其他输出),然后开始打印请求的时间,而且可以看出时间越来越大(但不是单调递增的,这点很怪)

13893 次点击
所在节点    Node.js
17 条回复
breeswish
2015-09-29 20:27:54 +08:00
使用 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);
});
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 行的请求返回
....
gzlock
2015-09-29 21:40:30 +08:00
@breeswish 现在有采集需求,当采集过程中发现新链接,要提交给任务队列
我用 child_process 实现了任务队列和进程池,跟 async 相比,该用哪个呢?
breeswish
2015-09-29 22:18:26 +08:00
@gzlock async 的 queue 是动态的
gzlock
2015-09-29 22:42:53 +08:00
@breeswish 我做的任务队列也是动态的, child_process 可以通过 process.send({type:'newMission',url:'aaaa.com/?a=2'})发送新任务给主进程,由主进程添加到任务队列
gzlock
2015-09-29 22:48:55 +08:00
@gzlock 看来还是要试试 async 才能下决定了
breeswish
2015-09-29 23:47:07 +08:00
@gzlock 如果你已经造好轮子的话么你自己决定咯…反正 async 做流程控制是现成的库, async 可以充分发挥 Node.js 异步并发特性。你这么玩是传统的单线程思路,问题不大,没发挥 Node.js 优势而已
gzlock
2015-09-30 00:52:02 +08:00
@breeswish 主要是担忧 nodejs 单进程的异步性能,是否可以发挥出 cpu 的多线程计算能力?
gzlock
2015-09-30 00:52:42 +08:00
@breeswish 当然更主要造轮子前不知道有 async 这个库
ysmood
2015-09-30 03:05:53 +08:00
我之前写爬虫都是用 Promise 控制流,比 async 要灵活多了,配合 ES7 的 async-await 语法直接甩 async 一条街。可以试试这个库 https://github.com/ysmood/yaku#asynclimit-list-saveresults-progress
ysmood
2015-09-30 03:10:15 +08:00
https://github.com/ysmood/nokit/blob/master/examples/threadPool.coffee 这是我写的一个使用上面提到函数的示例,典型的 producer broker consumer 模型,会无穷无尽的爬下去。
magicyu1986
2015-09-30 09:23:21 +08:00
最好用一个信号量来控制请求速度,不然瞬间发一大堆请求,失败率肯定会增高.
morefreeze
2015-09-30 11:29:41 +08:00
@breeswish 感谢提出的 async 的库
我在用的时候,发现在 rl.on('line')时, push 到 queue 里,但我如果在最前面定义 queue.drain 却并没有出现完成的情况 这是为什么?
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
morefreeze
2015-09-30 14:03:46 +08:00
@breeswish 是因为我传了 callback 加了参数。
所以这个 callback 是有什么用呢,如果无法加参数的话
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` 应该是不影响整体流程的..
morefreeze
2015-09-30 14:45:16 +08:00
@breeswish 我找到原因了,因为我有句判断如果出错就直接 return 没有调用 callback ,所以没触发 drain ,另外 callback 参数如你所说,是可以正常处理的
多谢指导

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/224598

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX