如何使用 Puppeteer 等待所有下载完成?

How to wait for all downloads to complete with Puppeteer?

我有一个小型网络抓取应用程序,可以从 URL 需要访问页面的网络应用程序下载多个文件。

如果我在两次运行之间保持浏览器实例处于活动状态,它工作正常,但我想在两次运行之间关闭该实例。当我调用 browser.close() 时,我的下载停止了,因为 chrome 实例在下载完成之前关闭了。

puppeteer 是否提供一种方法来检查下载是否仍处于活动状态并等待它们完成?我试过 page.waitForNavigation({ waitUntil: "networkidle0" })"networkidle2",但它们似乎要无限期地等待。


尝试 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
        }
    }
})