如何使用 headless: true 下载带有 puppeteer 的文件?

How to download file with puppeteer using headless: true?

为了从网站 http://niftyindices.com/resources/holiday-calendar:

下载 csv 文件,我已经 运行 以下代码
const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();

await page.goto('http://niftyindices.com/resources/holiday-calendar');
await page._client.send('Page.setDownloadBehavior', {behavior: 'allow', 
downloadPath: '/tmp'})
await page.click('#exportholidaycalender');
await page.waitFor(5000);
await browser.close();
})();

headless: false 一起工作,它将文件下载到 /Users/user/Downloads。使用 headless: true 它不起作用。

我 运行 在 macOS Sierra (MacBook Pro) 上使用 puppeteer 版本 1.1.1 将 Chromium 版本 66.0.3347.0 拉入 .local-chromium/ 目录并使用 npm initnpm i --save puppeteer 进行设置。

知道哪里出了问题吗?

提前感谢您的时间和帮助,

此页面通过创建以逗号分隔的字符串并通过像这样设置数据类型强制浏览器下载它来下载 csv

let uri = "data:text/csv;charset=utf-8," + encodeURIComponent(content);
window.open(uri, "Some CSV");

chrome 这会打开一个新标签页。

您可以参与此活动并将内容实际下载到文件中。不确定这是否是最好的方法,但效果很好。

const browser = await puppeteer.launch({
  headless: true
});
browser.on('targetcreated', async (target) => {
    let s = target.url();
    //the test opens an about:blank to start - ignore this
    if (s == 'about:blank') {
        return;
    }
    //unencode the characters after removing the content type
    s = s.replace("data:text/csv;charset=utf-8,", "");
    //clean up string by unencoding the %xx
    ...
    fs.writeFile("/tmp/download.csv", s, function(err) {
        if(err) {
            console.log(err);
            return;
        }
        console.log("The file was saved!");
    }); 
});

const page = await browser.newPage();
.. open link ...
.. click on download link ..

我需要从登录后下载一个文件,该文件由 Puppeteer 处理。 targetcreated 没有被触发。在从 Puppeteer 实例复制 cookie 之后,我最终使用 request 下载了

在这种情况下,我正在流式传输文件,但您也可以轻松保存它。

    res.writeHead(200, {
        "Content-Type": 'application/octet-stream',
        "Content-Disposition": `attachment; filename=secretfile.jpg`
    });
    let cookies = await page.cookies();
    let jar = request.jar();
    for (let cookie of cookies) {
        jar.setCookie(`${cookie.name}=${cookie.value}`, "http://secretsite.com");
    }
    try {
        var response = await request({ url: "http://secretsite.com/secretfile.jpg", jar }).pipe(res);
    } catch(err) {
        console.trace(err);
        return res.send({ status: "error", message: err });
    }

我花了几个小时仔细研究 this thread and Stack Overflow yesterday, trying to figure out how to get Puppeteer to download a csv file by clicking a download link in headless mode in an authenticated session. The accepted answer here didn't work in my case because the download does not trigger targetcreated, and the next answer, for whatever reason, did not retain the authenticated session. This article 挽救了这一天。简而言之,fetch。希望这可以帮助其他人。

const res = await this.page.evaluate(() =>
{
    return fetch('https://example.com/path/to/file.csv', {
        method: 'GET',
        credentials: 'include'
    }).then(r => r.text());
});

问题是浏览器在下载完成前关闭。

您可以从响应中获取文件大小和文件名,然后使用监视脚本检查下载文件的文件大小,以关闭浏览器。

这是一个例子:

    const filename = "set this with some regex in response";
    const dir = "watch folder or file";
    
    // Download and wait for download
        await Promise.all([
            page.click('#DownloadFile'),
           // Event on all responses
            page.on('response', response => {
                // If response has a file on it
                if (response._headers['content-disposition'] === `attachment;filename=${filename}`) {
                   // Get the size
                    console.log('Size del header: ', response._headers['content-length']);
                    // Watch event on download folder or file
                     fs.watchFile(dir, function (curr, prev) {
                       // If current size eq to size from response then close
                        if (parseInt(curr.size) === parseInt(response._headers['content-length'])) {
                            browser.close();
                            this.close();
                        }
                    });
                }
            })
        ]);

虽然搜索响应的方式可以改进,但我希望你会发现这很有用。

我有另一个解决这个问题的方法,因为这里的 none 个答案对我有用。

我需要登录一个网站,然后下载一些 .csv 报告。有头的很好,无头的无论我尝试什么都失败了。查看网络错误,下载已中止,但我无法(快速)确定原因。

所以,我拦截了请求并使用 node-fetch 在 puppeteer 之外发出请求。这需要复制获取选项 body、headers 并添加访问 cookie。

祝你好运。

我找到了一种等待浏览器下载文件功能的方法。这个想法是等待谓词的响应。在我的例子中 URL 以 '/data' 结尾。

我只是不喜欢将文件内容加载到缓冲区中。

await page._client.send('Page.setDownloadBehavior', {
    behavior: 'allow',
    downloadPath: download_path,
});

await frame.focus(report_download_selector);
await Promise.all([
    page.waitForResponse(r => r.url().endsWith('/data')),
    page.keyboard.press('Enter'),
]);

setDownloadBehaviorheadless: true 模式下工作正常,文件最终被下载,但在完成时抛出异常,所以对于我的情况,一个简单的包装器有助于忘记这个问题并得到完成的工作:

const fs = require('fs');    
function DownloadMgr(page, downloaddPath) {
    if(!fs.existsSync(downloaddPath)){
        fs.mkdirSync(downloaddPath);
    }
    var init = page.target().createCDPSession().then((client) => {
        return client.send('Page.setDownloadBehavior', {behavior: 'allow', downloadPath: downloaddPath})
    });
    this.download = async function(url) {
        await init;
        try{
            await page.goto(url);
        }catch(e){}
        return Promise.resolve();
    }
}

var path = require('path');
var DownloadMgr = require('./classes/DownloadMgr');
var downloadMgr = new DownloadMgr(page, path.resolve('./tmp'));
await downloadMgr.download('http://file.csv');

我发现的一种方法是使用 addScriptTag 方法。在 FalseTrue

中都可以无头工作

使用这个可以下载任何类型的网页。现在考虑到网页打开了一个 link 类似的东西:https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4

网页,表示将使用以下脚本下载 mp4 文件;

    await page.addScriptTag({'content':'''
    function fileName(){
        link = document.location.href
        return link.substring(link.lastIndexOf('/')+1);
    }
    async function save() {
        bl = await fetch(document.location.href).then(r => r.blob()); 
        var a = document.createElement("a");
        a.href = URL.createObjectURL(bl);
        a.download = fileName();
        a.hidden = true;
        document.body.appendChild(a);
        a.innerHTML = "download";
        a.click();
    }
    save()
    '''
    })

我有一个更难的变体,使用 Puppeteer Sharp。我需要在下载开始前设置 HeadersCookies

本质上,在单击按钮之前,我必须处理多个响应并处理下载时的单个响应。收到特定响应后,我必须附加 headers 和 cookie 以便远程服务器在响应中发送可下载数据。

await using (var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true, Product = Product.Chrome }))
await using (var page = await browser.NewPageAsync())
{
    ...
    // Handle multiple responses and process the Download
    page.Response += async (sender, responseCreatedEventArgs) =>
    {
        if (!responseCreatedEventArgs.Response.Headers.ContainsKey("Content-Type"))
            return;

        // Handle the response with the Excel download
        var contentType = responseCreatedEventArgs.Response.Headers["Content-Type"];
        if (contentType.Contains("application/vnd.ms-excel"))
        {
            string getUrl = responseCreatedEventArgs.Response.Url;

            // Add the cookies to a container for the upcoming Download GET request
            var pageCookies = await page.GetCookiesAsync();
            var cookieContainer = BuildCookieContainer(pageCookies);

            await DownloadFileRequiringHeadersAndCookies(getUrl, fullPath, cookieContainer, cancellationToken);
        }
    };

    await page.ClickAsync("button[id^='next']");

    // NEED THIS TIMEOUT TO KEEP THE BROWSER OPEN WHILE THE FILE IS DOWNLOADING!
    await page.WaitForTimeoutAsync(1000 * configs.DownloadDurationEstimateInSeconds);
}

像这样填充 Cookie 容器:

private CookieContainer BuildCookieContainer(IEnumerable<CookieParam> cookies)
{
    var cookieContainer = new CookieContainer();
        
    foreach (var cookie in cookies)
    {
        cookieContainer.Add(new Cookie(cookie.Name, cookie.Value, cookie.Path, cookie.Domain));
    }

    return cookieContainer;
}

DownloadFileRequiringHeadersAndCookies的详细信息是here。如果您需要更简单地下载文件,您可以使用此线程或链接线程中提到的其他方法。