2

我需要下载约 5 万个网页,从中获取一些数据并将其放入变量中。

我将每个请求包装到 Promise 中,然后将Promise.all()它们包装起来。我使用Request图书馆。

简化代码:

const request = require('request');
const urls = [url1, url2, ...];
const promises = [];

urls.forEach(url => {
    promises.push((resolve, reject) => {
        request(url, (error, response, body) => {
            if(error){ reject(error); return; }

            // do something with page

            resolve(someData);
        });
    });
});

Promise.all(promises.map(pr => new Promise(pr)))
    .then((someDataArray)=>{ /* process data /* });

但是我收到ENFILE异常,这代表系统中打开的文件太多(在我的桌面上打开文件的最大数量为 2048)。

我知道 Promises 在创建时执行,但我无法解决这个问题。

也许还有其他方法可以做到这一点?感谢您的回复。

4

5 回答 5

3

您想要的是启动 N 个请求,然后在一个完成时启动一个新请求(无论成功与否)。

有很多库可以做到这一点,但重要的是能够自己实现这种限制:

const request = require('request');
const urls = [url1, url2, ...];
const MAX_QUERIES = 10;
var remaining = urls.length;

const promises = [];

function startQuery(url){
    if (!url) return;
    request(url, (error, response, body) => {
        if (error) // handle error
        else // handle result
        startQuery(urls.shift());
        if (--remaining==0) return allFinished();
    });
}

for (var i=0; i<MAX_QUERIES; i++) startQuery(urls.shift());

function allFinished(){
    // all done
}
于 2017-05-09T12:06:42.740 回答
1

您可以使用 async.forEachLimit 尝试此操作,您可以在其中定义请求数量的限制。上一批完成后,它将执行下一批受限请求。

使用安装包npm install --save async

async.forEachLimit(urls, 50,function(url, callback) {
    //process url using request module
    callback();
}, function(err) {
    if (err) return next(err);
    console.log("All urls are processed");
});

如需进一步帮助,请查看:https ://caolan.github.io/async/docs.html

于 2017-05-09T12:02:48.657 回答
0

安装异步包并使用 forEachLimit 来限制操作数。

const request = require('request');
const urls = [];
for(var temp=0;temp<1024;temp++){
  urls.push("http://www.google.com");
}
const async = require("async");
const promises = [];
var i=0;
async.forEachLimit(urls, 10, function(url, callback) {
  request(url, (error, response, body) => {
    if (error) {
      callback(error);
      return;
    }

    var somedata = null;
    console.log(++i);
    callback(null, somedata);
  });
}, function(err) {
  /* process data */ 
});
于 2017-05-09T12:07:52.493 回答
0

就像评论中说的,你可以使用 async.js 模块

const request = require('request');
const async = require('async');

var listOfUrls = [url1, url2, ...];

async.mapLimit(listOfUrls, 10, function(url, callback) {
  // iterator function
  request(url, function(error, response, body) {
    if (!error && response.statusCode == 200) {
      var dataFromPage = ""; // get data from the page
      callback(null, arrToCheck);
    } else {
      callback(error || response.statusCode);
    }
  });
}, function(err, results) {
  // completion function
  if (!err) {
    // process all results in the array here
    // Do something with the data
    resolve(results);
  } else {
    // handle error here
    console.log(err);
  }
});

在这里您将一次处理 10 个 url,当所有 url 都已处理后,将调用结果回调,您可以在其中处理您的数据

于 2017-05-09T12:10:05.633 回答
0

其他人已经说过如何使用 async 或 Promise 进行流控制,我不再赘述。就个人而言,我更喜欢异步 JS 方法,但这只是我的偏好。

然而,他们没有涵盖的两件事,如果你希望你的脚本高性能和可靠,我认为这与流量控制一样重要。

1) 不要依赖回调或承诺来处理文件。到目前为止提供的所有示例都使用这些示例。我自己,我会使用请求流 API 来将请求视为可读流并将该流通过管道传输到处理它的可写对象。最简单的例子是使用 fs 将文件写入文件系统。这可以更好地利用您的系统资源,因为它将每个数据块写入存储,而不是必须将整个文件保存在内存中。然后,您可以在流结束时调用回调或解决承诺。

2)您不应该尝试处理并在内存列表中存储 50k URL。如果您这样做并且失败了,假设是第 20,000 个 URL,那么您必须弄清楚如何从未完成的 URL 中挑选出已完成的 URL,并更新您的代码或您从中读取它们的 JSON 文件。相反,使用具有集合/表/任何 URL 和有关它们的元数据的数据库(任何都可以)。当你的程序运行时,查询那些没有表明它们已成功获取的属性,然后当你成功获取它们或请求失败时,你可以使用相同的数据结构来为你提供一些关于为什么它的智能失败或成功时。

于 2017-05-09T12:22:14.650 回答