循环使用可变 URL 的 api 获取请求

Posted

技术标签:

【中文标题】循环使用可变 URL 的 api 获取请求【英文标题】:Loop through an api get request with variable URL 【发布时间】:2018-07-28 07:03:58 【问题描述】:

我正在尝试调用 CompaniesHouse API 并获取在 11 月至 2 月之间注册的公司。我采用的方法是选择一个起始索引(11 月注册的公司)和一个停止索引(2 月注册的公司),然后循环获取在起始索引和停止索引之间注册的公司。像这样:

var needle = require("needle");
var startIdx = 11059000;
var stopIdx  = 11211109;
for(idx = startIdx; idx < stopIdx; idx++)

    needle('get', "https://api.companieshouse.gov.uk/company/"+idx,  
       username: key,password:"" 
    )
   .then(function(data) 

   )
  .catch(function(err) 
    console.log('Call the locksmith!' + err)
  )

但这不起作用,因为会出现超时或套接字挂断错误。

API 目前处于测试阶段,一些功能仍有待实现。

【问题讨论】:

参考这个***.com/questions/48832718/… @RahulSharma 这似乎是一个数据库提取,我不明白它与此有何关系。你能解释一下吗? 只检查如何循环异步调用,而不是db调用你调用api。 使用 Promise.all 【参考方案1】:

因为for 循环同步运行并且您对needle() 的调用是异步的,因此不会阻塞,您最终会尝试同时启动超过100,000 个网络请求。这会使您的本地计算机或目标服务器不堪重负,并且您开始收到套接字错误。

对于这么多请求,您需要一次运行 X 个请求,因此同时运行的请求不超过 X 个。为了最大限度地提高性能,您必须弄清楚要使用的 X 值,因为它取决于目标服务器以及它如何处理大量同时请求。通常从值 5 开始是安全的,然后从那里增加它以测试更高的值。

如果您正在处理一个数组,则有许多预先构建的选项可以同时运行 X 请求。最简单的是使用预先构建的并发管理操作,例如 Bluebird。或者你可以自己写。您可以在此处查看两者的示例:Make several requests to an API that can only handle 20 request a minute

但是,由于您没有处理数组,而只是为每个连续的请求增加一个数字,所以我找不到执行此操作的预构建选项。所以,我写了一个通用的,你可以在其中填写将增加索引的函数:

// fn gets called on each iteration - must return a promise
// limit is max number of requests to be in flight at once
// cnt is number of times to call fn
// options is optional and can be continueOnError: true
// runN returns a promise that resolves with results array.  
// If continueOnError is set, then results array 
// contains error values too (presumed to be instanceof Error so caller can discern
// them from regular values)
function runN(fn, limit, cnt, options = ) 
    return new Promise((resolve, reject) => 
        let inFlightCntr = 0;
        let results = [];
        let cntr = 0;
        let doneCnt = 0;

        function run() 
            while (inFlightCntr < limit && cntr < cnt) 
                let resultIndex = cntr++;
                ++inFlightCntr;
                fn().then(result => 
                    --inFlightCntr;
                    ++doneCnt;
                    results[resultIndex] = result;
                    run();          // run any more that still need to be run
                ).catch(err => 
                    --inFlightCntr;
                    ++doneCnt;
                    if (options.continueOnError) 
                        // assumes error is instanceof Error so caller can tell the
                        // difference between a genuine result and an error
                        results[resultIndex] = err;       
                        run();          // run any more that still need to be run
                     else 
                        reject(err);
                    
                );
            
            if (doneCnt === cnt) 
                resolve(results);
            
        
        run();
    );

然后,你可以这样使用:

const needle = require("needle");
const startIdx = 11059000;
const stopIdx  = 11211109;
const numConcurrent = 5;
let idxCntr = startIdx;

runN(function() 
    let idx = idxCntr++;
    return needle('get', "https://api.companieshouse.gov.uk/company/"+idx,  
        username: key,password:"" 
    );
, numConcurrent, stopIdx - startIdx + 1, continueOnError: true).then(results => 
    console.log(results);
).catch(err => 
    console.log(err);
);

为了最大程度地减少内存使用,您可以在调用 needle() 时使用 .then() 处理程序,并将响应缩减为仅在最终数组中需要的内容:

const needle = require("needle");
const startIdx = 11059000;
const stopIdx  = 11211109;
const numConcurrent = 5;
let idxCntr = startIdx;

runN(function() 
    let idx = idxCntr++;
    return needle('get', "https://api.companieshouse.gov.uk/company/"+idx,  
        username: key,password:"" 
    ).then(response => 
        // construct the smallest possible response here and then return it
        // to minimize memory use for your 100,000+ requests
        return response.someProperty;
    );
, numConcurrent, stopIdx - startIdx + 1, continueOnError: true).then(results => 
    console.log(results);
).catch(err => 
    console.log(err);
);

【讨论】:

有什么办法可以优化代码吗?我收到内存不足错误。 @imraj - 对于所有使用needle() 的网站,您会得到什么?您是否需要它返回的所有内容,或者您​​可以将其缩减以仅保留少量数据。您正在收集超过 100,000 个的数组。如果里面有很多东西,那么就会占用大量内存。如果你告诉我你得到了什么以及你真正需要保留什么,那么我可以提供关于保留更少的建议,这样它就可以使用更少的内存。或者,您可以做 1000 个,收集结果,处理它们,然后删除数据,然后再做 1000 个。 @imraj - 查看我在答案末尾添加的与内存使用有关的内容。【参考方案2】:
var needle = require("needle");
var startIdx = 11059000;
var stopIdx  = 11211109;
const promises = [];
for(idx = startIdx; idx < stopIdx; idx++)

    promises.push(
        needle('get', "https://api.companieshouse.gov.uk/company/"+idx,  
            username: key,password:"" 
        )
    )


Promise.all(promises).then(results => console.log(results);).catch(err => console.log(err));

一个简单的Promise.all 实现会有所帮助。

【讨论】:

这并不能阻止 OP 的问题。他们的问题是他们同时有太多的请求。

以上是关于循环使用可变 URL 的 api 获取请求的主要内容,如果未能解决你的问题,请参考以下文章

哪个PayPal API用于可变结算

循环遍历不可变的 JS 记录

字符串类

retrofit和okhttp请求url的参数拼接

使用可变文件名将图像保存在循环中?

杂记1