我编写了一个Node.js脚本,该脚本使用download
,axios
和fs
模块从Federal Register提供的JSON中提取网址,然后下载相关的PDF文件。但是,该脚本通常无法下载所有PDF。
无论出于何种原因,我的脚本都会“死机”,然后下载所有PDF文件。意思是,它开始时很棒(下载了70、80个文件),但随后停滞了。它不会触发我的捕获块,也不会以任何方式失败。它只是停止下载。
文件数取决于我所连接的wifi。但是,我从未能够完成代码并在代码中触发.then
块。理想情况下,下载文件后,我想使用.then块进行处理。
代码如下:
// The callback function that writes the file...
function writeFile(path, contents, cb){
mkdirp(getDirName(path), function(err){
if (err) return cb(err)
fs.writeFile(path, contents, cb)
})
};
// The function that gets the JSON...
axios.get(`http://federalregister.gov/api/v1/public-inspection-documents.json?conditions%5Bavailable_on%5D=${today}`)
.then(downloadPDFS)
.catch((err) => {
console.log("COULD NOT DOWNLOAD FILES: \n", err);
});
// The function that downloads the data and triggers my write callback...
function downloadPDFS(res) {
const downloadPromises = res.data.results.map(item => (
download(item.pdf_url)
.then(data => new Promise((resolve, reject) => {
writeFile(`${__dirname}/${today}/${item.pdf_file_name}`, data, (err) => {
if(err) reject(err);
else resolve(console.log("FILE WRITTEN: ", item.pdf_file_name));
});
}))
))
return Promise.all(downloadPromises).then((res) => console.log("DONE"))
}
我的项目在Github here上,以防您想要安装并自己尝试。以下是使用简单英语进行的操作摘要:
该脚本从服务器获取JSON,该服务器包含所有126个PDF的URL。然后,将这些URL的数组传递给同步map
函数。每个网址都通过download
模块转换为一个Promise。该承诺将隐式返回,并存储在Promise.all
包装器中。当下载承诺解决(文档已完成下载)后,我的自定义writeFile函数将触发,并使用下载的数据写入PDF文件。下载完所有文件后,Promise.all
包装程序应解决。但这不会发生。
出了什么问题?
编辑-
如下所示,该脚本运行了一段时间,但随后停顿了下来,不再下载其他文件...
答案 0 :(得分:1)
如果确实是费率问题,那么有几种方法可以解决它(取决于API如何限制费率)
下面共有3个解决方案
rateLimited
...这会触发每秒限制为给定请求数的请求
singleQueue
...一次只有一个请求,没有速率限制,只有一系列请求
multiQueue
...一次最多给定数量的“运行中”请求
const rateLimited = perSecond => {
perSecond = isNaN(perSecond) || perSecond < 0.0001 ? 0.0001 : perSecond;
const milliSeconds = Math.floor(1000 / perSecond);
let promise = Promise.resolve(Date.now);
const add = fn => promise.then(lastRun => {
const wait = Math.max(0, milliSeconds + lastRun - Date.now);
promise = promise.thenWait(wait).then(() => Date.now);
return promise.then(fn);
});
return add;
};
const singleQueue = () => {
let q = Promise.resolve();
return fn => q = q.then(fn);
};
const multiQueue = length => {
length = isNaN(length) || length < 1 ? 1 : length;
const q = Array.from({ length }, () => Promise.resolve());
let index = 0;
const add = fn => {
index = (index + 1) % length;
return q[index] = q[index].then(fn);
};
return add;
};
// uncomment one, and only one, of the three "fixup" lines below
let fixup = rateLimited(10); // 10 per second for example
//let fixup = singleQueue; // one at a time
//let fixup = multiQueue(6); // at most 6 at a time for example
const writeFile = (path, contents) => new Promise((resolve, reject) => {
mkdirp(getDirName(path), err => {
if (err) return reject(err);
fs.writeFile(path, contents, err => {
if (err) return reject(err);
resolve();
})
})
});
axios.get(`http://federalregister.gov/api/v1/public-inspection-documents.json?conditions%5Bavailable_on%5D=${today}`)
.then(downloadPDFS)
.catch((err) => {
console.log("COULD NOT DOWNLOAD FILES: \n", err);
});
function downloadPDFS(res) {
const downloadPromises = res.data.results.map(item => fixup(() =>
download(item.pdf_url)
.then(data => writeFile(`${__dirname}/${today}/${item.pdf_file_name}`, data))
.then(() => console.log("FILE WRITTEN: ", item.pdf_file_name))
));
return Promise.all(downloadPromises).then(() => console.log("DONE"));
}
我还对代码进行了一些重构,因此downloadPDFS
仅使用promise-所有节点回调样式代码都放入writeFile
答案 1 :(得分:0)
正如Jaromanda指出的那样,这很可能与API限制了我的访问权限有关,而不是与脚本错误有关。
我在脚本中添加了一个过滤器,以选择较少的数据,并且它可以正常工作。如下:
axios.get(`http://federalregister.gov/api/v1/public-inspection-documents.json?conditions%5Bavailable_on%5D=${today}`)
.then(downloadPDFS)
.then(() => {
console.log("DONE")
})
.catch((err) => {
console.log("COULD NOT DOWNLOAD FILES: \n", err);
});
function downloadPDFS(res) {
const EPA = res.data.results.filter((item) => {
return item.agencies[0].raw_name === "ENVIRONMENTAL PROTECTION AGENCY"; //// THIS FILTER
});
const downloadPromises = EPA.map(item => ( //// ONLY DOWNLOADING SOME OF THE DATA
download(item.pdf_url)
.then(data => new Promise((resolve, reject) => {
writeFile(`${__dirname}/${today}/${item.pdf_file_name}`, data, (err) => {
if(err) reject(err);
else resolve(console.log("FILE WRITTEN: ", item.pdf_file_name));
});
}))
))
return Promise.all(downloadPromises)
}