如何使用headless下载文件与puppeteer:true?

时间:2018-03-12 22:00:37

标签: node.js chromium puppeteer

我一直在运行以下代码,以便从网站csv下载http://niftyindices.com/resources/holiday-calendar文件:

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)上使用木偶版本1.1.1运行此操作,将Chromium版本66.0.3347.0拉入.local-chromium/目录并使用npm initnpm i --save puppeteer进行设置。

任何想法都错了吗?

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

9 个答案:

答案 0 :(得分:6)

此页面通过创建逗号分隔字符串并强制浏览器通过设置数据类型来下载它来下载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 ..

答案 1 :(得分:4)

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

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

这是一个示例:

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();
                    }
                });
            }
        })
    ]);

尽管我希望您会发现有用,但即使可以改善响应搜索的方式。

答案 2 :(得分:2)

昨天我花了几个小时研究this thread和Stack Overflow,试图找出如何通过在经过身份验证的会话中单击无头模式下载链接来让Puppeteer下载csv文件。此处接受的答案在我的情况下不起作用,因为下载不会触发targetcreated,并且无论出于何种原因,下一个答案都不会保留经过身份验证的会话。 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());
});

答案 3 :(得分:2)

我找到了一种等待浏览器功能下载文件的方法。这个想法是等待谓词的响应。在我的情况下,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'),
]);

答案 4 :(得分:0)

我需要从登录后面下载一个文件,该文件由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 });
    }

答案 5 :(得分:0)

我对此问题有另一种解决方案,因为这里没有答案对我有用。

我需要登录一个网站,并下载一些.csv报告。 Headed很好,无论我尝试什么,headless都失败了。查看网络错误,下载被中止,但是我无法(迅速)确定原因。

因此,我拦截了请求,并使用node-fetch在伪造者之外进行了请求。这需要复制获取选项,正文,标头并添加访问cookie。

祝你好运。

答案 6 :(得分:0)

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');

答案 7 :(得分:0)

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

使用此可以下载任何类型的网页。现在考虑到网页打开了一个链接,如: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()
    '''
    })

答案 8 :(得分:0)

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

本质上,在单击按钮之前,我必须处理多个响应并通过下载处理单个响应。获得特定响应后,我必须为远程服务器附加标头和 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。如果您需要更简单的下载文件,您可能可以使用此线程或链接线程中提到的其他方法。