如何使用 Puppeteer 等待所有下载完成?
How to wait for all downloads to complete with Puppeteer?
我有一个小型网络抓取应用程序,可以从 URL 需要访问页面的网络应用程序下载多个文件。
如果我在两次运行之间保持浏览器实例处于活动状态,它工作正常,但我想在两次运行之间关闭该实例。当我调用 browser.close()
时,我的下载停止了,因为 chrome 实例在下载完成之前关闭了。
puppeteer 是否提供一种方法来检查下载是否仍处于活动状态并等待它们完成?我试过 page.waitForNavigation({ waitUntil: "networkidle0" })
和 "networkidle2"
,但它们似乎要无限期地等待。
- node.js 8.10
- 木偶师 1.10.0
尝试 await page.waitFor(50000);
下载所需的时间。
或者看看watching for file changes on complete file transfer
如果您有文件名或其他检查方法的建议,则可以选择。
async function waitFile (filename) {
return new Promise(async (resolve, reject) => {
if (!fs.existsSync(filename)) {
await delay(3000);
await waitFile(filename);
resolve();
}else{
resolve();
}
})
}
function delay(time) {
return new Promise(function(resolve) {
setTimeout(resolve, time)
});
}
实施:
var filename = `${yyyy}${mm}_TAC.csv`;
var pathWithFilename = `${config.path}\${filename}`;
await waitFile(pathWithFilename);
使用 puppeteer 和 chrome 我还有一个可能对您有帮助的解决方案。
如果您从 chrome 下载文件,它将始终具有“.crdownload”扩展名。当文件完全下载后,该扩展名将消失。
所以,我正在使用循环函数和它可以迭代的最大次数,如果它在那段时间没有下载文件..我正在删除它。我一直在检查该扩展名的文件夹。
async checkFileDownloaded(path, timer) {
return new Promise(async (resolve, reject) => {
let noOfFile;
try {
noOfFile = await fs.readdirSync(path);
} catch (err) {
return resolve("null");
}
for (let i in noOfFile) {
if (noOfFile[i].includes('.crdownload')) {
await this.delay(20000);
if (timer == 0) {
fs.unlink(path + '/' + noOfFile[i], (err) => {
});
return resolve("Success");
} else {
timer = timer - 1;
await this.checkFileDownloaded(path, timer);
}
}
}
return resolve("Success");
});
}
您需要检查请求响应。
await page.on('response', (response)=>{ console.log(response, response._url)}
您应该检查响应的内容然后查找状态,它带有 status 200
我的解决方案是使用 chrome 自己的 chrome://downloads/
页面来管理下载文件。这个解决方案可以很容易地使用 chrome 自己的功能
自动重启失败的下载
此示例当前为 'single thread',因为它仅监控下载管理器页面中出现的第一个项目。但是您可以通过遍历该页面中的所有下载项目 (#frb0
~#frbn
) 轻松地使其适应 'infinite threads',好吧,照顾好您的网络:)
dmPage = await browser.newPage()
await dmPage.goto('chrome://downloads/')
await your_download_button.click() // start download
await dmPage.bringToFront() // this is necessary
await dmPage.waitForFunction(
() => {
// monitoring the state of the first download item
// if finish than return true; if fail click
const dm = document.querySelector('downloads-manager').shadowRoot
const firstItem = dm.querySelector('#frb0')
if (firstItem) {
const thatArea = firstItem.shadowRoot.querySelector('.controls')
const atag = thatArea.querySelector('a')
if (atag && atag.textContent === '在文件夹中显示') { // may be 'show in file explorer...'? you can try some ids, classess and do a better job than me lol
return true
}
const btn = thatArea.querySelector('cr-button')
if (btn && btn.textContent === '重试') { // may be 'try again'
btn.click()
}
}
},
{ polling: 'raf', timeout: 0 }, // polling? yes. there is a 'polling: "mutation"' which kind of async
)
console.log('finish')
创建了简单的 await 函数,可以快速检查文件或在 10 秒内超时
import fs from "fs";
awaitFileDownloaded: async (filePath) => {
let timeout = 10000
const delay = 200
return new Promise(async (resolve, reject) => {
while (timeout > 0) {
if (fs.existsSync(filePath)) {
resolve(true);
return
} else {
await HelperUI.delay(delay)
timeout -= delay
}
}
reject("awaitFileDownloaded timed out")
});
},
这是另一个功能,它只是等待暂停按钮消失:
async function waitForDownload(browser: Browser) {
const dmPage = await browser.newPage();
await dmPage.goto("chrome://downloads/");
await dmPage.bringToFront();
await dmPage.waitForFunction(() => {
try {
const donePath = document.querySelector("downloads-manager")!.shadowRoot!
.querySelector(
"#frb0",
)!.shadowRoot!.querySelector("#pauseOrResume")!;
if ((donePath as HTMLButtonElement).innerText != "Pause") {
return true;
}
} catch {
//
}
}, { timeout: 0 });
console.log("Download finished");
}
您可以使用node-watch来报告目标目录的更新。文件上传完成后,您将收到一个更新事件,其中包含已下载的新文件的名称。
运行 npm 安装 node-watch:
npm install node-watch
示例代码:
const puppeteer = require('puppeteer');
const watch = require('node-watch');
const path = require('path');
// Add code to initiate the download ...
const watchDir = '/Users/home/Downloads'
const filepath = path.join(watchDir, "download_file");
(async() => {
watch(watchDir, function(event, name) {
if (event == "update") {
if (name === filepath)) {
browser.close(); // use case specific
process.exit(); // use case specific
}
}
})
我有一个小型网络抓取应用程序,可以从 URL 需要访问页面的网络应用程序下载多个文件。
如果我在两次运行之间保持浏览器实例处于活动状态,它工作正常,但我想在两次运行之间关闭该实例。当我调用 browser.close()
时,我的下载停止了,因为 chrome 实例在下载完成之前关闭了。
puppeteer 是否提供一种方法来检查下载是否仍处于活动状态并等待它们完成?我试过 page.waitForNavigation({ waitUntil: "networkidle0" })
和 "networkidle2"
,但它们似乎要无限期地等待。
- node.js 8.10
- 木偶师 1.10.0
尝试 await page.waitFor(50000);
下载所需的时间。
或者看看watching for file changes on complete file transfer
如果您有文件名或其他检查方法的建议,则可以选择。
async function waitFile (filename) {
return new Promise(async (resolve, reject) => {
if (!fs.existsSync(filename)) {
await delay(3000);
await waitFile(filename);
resolve();
}else{
resolve();
}
})
}
function delay(time) {
return new Promise(function(resolve) {
setTimeout(resolve, time)
});
}
实施:
var filename = `${yyyy}${mm}_TAC.csv`;
var pathWithFilename = `${config.path}\${filename}`;
await waitFile(pathWithFilename);
使用 puppeteer 和 chrome 我还有一个可能对您有帮助的解决方案。
如果您从 chrome 下载文件,它将始终具有“.crdownload”扩展名。当文件完全下载后,该扩展名将消失。
所以,我正在使用循环函数和它可以迭代的最大次数,如果它在那段时间没有下载文件..我正在删除它。我一直在检查该扩展名的文件夹。
async checkFileDownloaded(path, timer) {
return new Promise(async (resolve, reject) => {
let noOfFile;
try {
noOfFile = await fs.readdirSync(path);
} catch (err) {
return resolve("null");
}
for (let i in noOfFile) {
if (noOfFile[i].includes('.crdownload')) {
await this.delay(20000);
if (timer == 0) {
fs.unlink(path + '/' + noOfFile[i], (err) => {
});
return resolve("Success");
} else {
timer = timer - 1;
await this.checkFileDownloaded(path, timer);
}
}
}
return resolve("Success");
});
}
您需要检查请求响应。
await page.on('response', (response)=>{ console.log(response, response._url)}
您应该检查响应的内容然后查找状态,它带有 status 200
我的解决方案是使用 chrome 自己的 chrome://downloads/
页面来管理下载文件。这个解决方案可以很容易地使用 chrome 自己的功能
此示例当前为 'single thread',因为它仅监控下载管理器页面中出现的第一个项目。但是您可以通过遍历该页面中的所有下载项目 (#frb0
~#frbn
) 轻松地使其适应 'infinite threads',好吧,照顾好您的网络:)
dmPage = await browser.newPage()
await dmPage.goto('chrome://downloads/')
await your_download_button.click() // start download
await dmPage.bringToFront() // this is necessary
await dmPage.waitForFunction(
() => {
// monitoring the state of the first download item
// if finish than return true; if fail click
const dm = document.querySelector('downloads-manager').shadowRoot
const firstItem = dm.querySelector('#frb0')
if (firstItem) {
const thatArea = firstItem.shadowRoot.querySelector('.controls')
const atag = thatArea.querySelector('a')
if (atag && atag.textContent === '在文件夹中显示') { // may be 'show in file explorer...'? you can try some ids, classess and do a better job than me lol
return true
}
const btn = thatArea.querySelector('cr-button')
if (btn && btn.textContent === '重试') { // may be 'try again'
btn.click()
}
}
},
{ polling: 'raf', timeout: 0 }, // polling? yes. there is a 'polling: "mutation"' which kind of async
)
console.log('finish')
创建了简单的 await 函数,可以快速检查文件或在 10 秒内超时
import fs from "fs";
awaitFileDownloaded: async (filePath) => {
let timeout = 10000
const delay = 200
return new Promise(async (resolve, reject) => {
while (timeout > 0) {
if (fs.existsSync(filePath)) {
resolve(true);
return
} else {
await HelperUI.delay(delay)
timeout -= delay
}
}
reject("awaitFileDownloaded timed out")
});
},
这是另一个功能,它只是等待暂停按钮消失:
async function waitForDownload(browser: Browser) {
const dmPage = await browser.newPage();
await dmPage.goto("chrome://downloads/");
await dmPage.bringToFront();
await dmPage.waitForFunction(() => {
try {
const donePath = document.querySelector("downloads-manager")!.shadowRoot!
.querySelector(
"#frb0",
)!.shadowRoot!.querySelector("#pauseOrResume")!;
if ((donePath as HTMLButtonElement).innerText != "Pause") {
return true;
}
} catch {
//
}
}, { timeout: 0 });
console.log("Download finished");
}
您可以使用node-watch来报告目标目录的更新。文件上传完成后,您将收到一个更新事件,其中包含已下载的新文件的名称。
运行 npm 安装 node-watch:
npm install node-watch
示例代码:
const puppeteer = require('puppeteer');
const watch = require('node-watch');
const path = require('path');
// Add code to initiate the download ...
const watchDir = '/Users/home/Downloads'
const filepath = path.join(watchDir, "download_file");
(async() => {
watch(watchDir, function(event, name) {
if (event == "update") {
if (name === filepath)) {
browser.close(); // use case specific
process.exit(); // use case specific
}
}
})