在运行时处理50k网页(NodeJS)

时间:2017-05-09 11:51:54

标签: javascript node.js requestjs

我需要下载~50k网页,从中获取一些数据并将其变为变量。

我将每个请求包装到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会在创建时执行,但我无法解决这个问题。

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

5 个答案:

答案 0 :(得分: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
}

答案 1 :(得分: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

答案 2 :(得分:0)

安装async包并使用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 */ 
});

答案 3 :(得分: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个网址,当所有网址都已处理完毕后,系统会调用结果回调,您可以在其中处理数据

答案 4 :(得分:0)

其他人已经说过如何使用异步或承诺来进行流量控制,我不会重复它们。就个人而言,我更喜欢异步JS方法,但这只是我的偏好。

然而,如果你希望你的脚本具有高性能和可靠性,那么我认为它们与流量控制同样重要。

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

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