簡體   English   中英

如何使用 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 with headless: true它不起作用。

我正在使用66.0.3347.0版本1.1.1在 macOS Sierra(MacBook Pro)上運行它,它將 Chromium 版本66.0.3347.0拉入.local-chromium/目錄並使用npm initnpm i --save puppeteer進行設置。

知道出了什么問題嗎?

提前感謝您的時間和幫助,

我昨天花了幾個小時仔細研究這個線程和 Stack Overflow,試圖弄清楚如何通過在經過身份驗證的會話中以無頭模式單擊下載鏈接來讓 Puppeteer 下載 csv 文件。 這里接受的答案在我的情況下不起作用,因為下載不會觸發targetcreated ,並且下一個答案,無論出於何種原因,都沒有保留經過身份驗證的會話。 這篇文章拯救了這一天。 簡而言之, fetch 希望這可以幫助其他人。

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

此頁面通過創建逗號分隔的字符串並通過設置數據類型強制瀏覽器下載它來下載 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 ..

問題是瀏覽器在下載完成之前關閉。

您可以從響應中獲取文件大小和文件名,然后使用監視腳本從下載的文件中檢查文件大小,以關閉瀏覽器。

這是一個例子:

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

即使可以改進搜索響應的方式,但我希望您會發現這很有用。

我找到了一種等待瀏覽器功能下載文件的方法。 這個想法是用謂詞等待響應。 在我的例子中,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'),
]);

我需要從登錄后下載一個文件,這是由 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 });
    }

我有另一個解決這個問題的方法,因為這里的答案都不適合我。

我需要登錄一個網站,然后下載一些 .csv 報告。 Headed 很好,無論我嘗試什么,Headed 都失敗了。 查看網絡錯誤,下載已中止,但我無法(快速)確定原因。

因此,我攔截了請求並使用 node-fetch 在 puppeteer 之外發出請求。 這需要復制獲取選項、正文、標題並添加訪問 cookie。

祝你好運。

setDownloadBehavior適用於headless: 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

使用這個可以下載任何類型的網頁。 現在考慮到該網頁會打開一個類似於以下內容的鏈接: 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

本質上,在單擊按鈕之前,我必須處理多個響應並通過下載處理單個響應。 一旦我有了那個特定的響應,我就必須為遠程服務器附加標頭和 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的詳細信息在這里 如果您需要更簡單地下載文件,您可能可以使用此線程或鏈接線程中提到的其他方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM