页面在 Puppeteer 中看不到 cookie

Page doesn't see cookies in Puppeteer

为了任务清晰度而编辑:最后我从 Postgres 中提取库存数据和客户数据以呈现并向客户发送一堆 PDF,每月一次。 这些 PDF 是动态的,因为封面将有不同的客户 name/address。下一页也是动态的,因为它们是特定客户的过期库存列表,编号为 item/expirying date/serial。

我制作了一个带有打印 CSS 的客户端 React 页面,以呈现一些可以打印 off/saved 为漂亮 PDF 的打印布局字母。

然后,瀑布规范出现了,这将是服务器上的一个自动化过程。基本上,PDF 需要附加到电子邮件中,提醒客户产品过期(在医疗行业,一切都需要审计)。

我认为使用 Puppeteer 会是一个不错且简单的转换。只需添加一个处理所有客户的路由,查找任何可能过期的内容,然后将其传递到动态反应页面以无头地呈现为 PDF 文件(并最终完成整个计划的其余部分,发送电子邮件等) .现在我只抓取 10 个客户和他们的 PoC 到期库存,然后我基本上有:{ customer: {}, expiring: [] }.

我曾尝试使用 POST 进行带中断的分页,但我想这是有道理的,因为我无法在浏览器中获取 post 数据。 所以,我改变了使用 cookie 的方法。我希望这能工作,但我永远无法将 cookie 读入页面。

这是一个:简单的路线,简单的木偶操纵者,它将 cookie 写到 json 并截取屏幕截图作为证明,以及简单的 HTML 我正在使用的脚本只是为了尝试证明我可以传递数据。

server/index.js:

app.get('/testing', async (req, res) => {
    console.log('GET /testing');
    res.sendFile(path.join(__dirname, 'scratch.html'));
});

scratch.js(运行 在命令行 node ./scratch.js:

const puppeteer = require('puppeteer')
const fs = require('fs');
const myCookies = [{name: 'customer', value: 'Frank'}, {name: 'expiring', value: JSON.stringify([{a: 1, b: 'three'}])}];

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  await page.goto('http://localhost:1234/testing', { waitUntil: 'networkidle2' });
  await page.setCookie(...myCookies);

  const cookies = await page.cookies();
  const cookieJson = JSON.stringify(cookies);

  // Writes expected cookies to file for sanity check.
  fs.writeFileSync('scratch_cookies.json', cookieJson);
  
  // FIXME: Cookies never get appended to page.
  await page.screenshot({path: 'scratch_shot.png'});
  await browser.close();
})();

server/scratch.html:

<html>
    <body>
    </body>
    <script type='text/javascript'>
        document.write('Cookie: ' + document.cookie);
    </script>
</html>

结果只是一个带有“Cookie:”字样的 PNG。任何见解表示赞赏!

这是我在 makeExpiryLetter 使用 puppeteer 时使用的实际路线,但我似乎无法让它真正读取客户和行数据。

app.get('/create-expiry-letter', async (req, res) => {
    // Create PDF file using puppeteer to render React page w/ data.
    // Store in Db.
    // Email file.
    // Send final count of letters sent back for notification in GUI.
    const cc = await dbo.getConsignmentCustomers();
    const result = await Promise.all(cc.rows.map(async x => {
        // Get 0-60 day consignments by customer_id;
        const { rows } = await dbo.getExpiry0to60(x.customer_id);
        if (rows && rows.length > 0) {
            const epiryLetter = await makeExpiryLetter(x, rows); // Uses puppeteer.
            // TODO: Store in Db / Email file.
            return true;
        } else {
            return false;
        }
    }));
    res.json({ emails_sent: result.filter(x => x === true).length });
});

感谢@ggorlen 提供的示例,我在使用 cookie 方面取得了巨大进展。在我的 expiry.html 内联脚本中,我通过将渲染函数包装在 function main () 中并将 onload 添加到 body 标记 <body onload='main()' 来获取值。 在主函数中我们可以获取我需要的值:

const customer = JSON.parse(document.cookie.split('; ').find(row => row.startsWith('customer')).split('=')[1]);
const expiring = JSON.parse(document.cookie.split('; ').find(row => row.startsWith('expiring')).split('=')[1]);

最后(是的,当然这最终都会用在自动化工作者中)我可以像这样获得精美渲染的 PDF:

(async () => {
  const browser = await puppeteer.launch();
  const [page] = await browser.pages();
  await page.setCookie(...myCookies);
  await page.goto('http://localhost:1234/testing');
  await page.pdf({ path: `scratch-expiry-letter.pdf`, format: 'letter' });
  await browser.close();
})();

问题在这里:

await page.goto('http://localhost:1234/testing', { waitUntil: 'networkidle2' });
await page.setCookie(...myCookies);

第一行说,转到页面。转到页面涉及解析 HTML 和执行脚本,包括 scratch.html 中的 document.write('Cookie: ' + document.cookie); 行,此时页面上没有 cookie(假设浏览器缓存清晰)。

页面加载后,await page.goto... returns 和 await page.setCookie(...myCookies); 运行 行。这会正确设置您的 cookie 并执行其余行。 const cookies = await page.cookies(); 运行s 并拉出新设置的 cookie,然后将它们写入磁盘。 await page.screenshot({path: 'scratch_shot.png'}); 运行s,在没有 DOM 使用初始 document.write 调用后设置的新 cookie 更新的情况下拍摄页面。

您可以通过将 scratch.html 页面上的 JS 转换为可以在页面加载和设置 cookie 后调用的函数来解决此问题,或者使用 [=21= 使用 Puppeteer 动态注入这样的函数]:

const puppeteer = require('puppeteer');

const myCookies = [
  {name: 'customer', value: 'Frank'}, 
  {name: 'expiring', value: JSON.stringify([{a: 1, b: 'three'}])}
];

(async () => {
  const browser = await puppeteer.launch();
  const [page] = await browser.pages();
  await page.goto('http://localhost:1234/testing');
  await page.setCookie(...myCookies);

  // now that the cookies are ready, we can write to the document
  await page.evaluate(() => document.write('Cookie' + document.cookie));

  await page.screenshot({path: 'scratch_shot.png'});
  await browser.close();
})();

更通用的方法是set the cookies before navigation。这样,当任何可能使用它们的脚本 运行.

时,cookie 将已经存在
const puppeteer = require('puppeteer');

const myCookies = [
  {
    name: 'expiring',
    value: '[{"a":1,"b":"three"}]',
    domain: 'localhost',
    path: '/',
    expires: -1,
    size: 29,
    httpOnly: false,
    secure: false,
    session: true,
    sameParty: false,
    sourceScheme: 'NonSecure',
    sourcePort: 80
  },
  {
    name: 'customer',
    value: 'Frank',
    domain: 'localhost',
    path: '/',
    expires: -1,
    size: 13,
    httpOnly: false,
    secure: false,
    session: true,
    sameParty: false,
    sourceScheme: 'NonSecure',
    sourcePort: 80
  }
];

(async () => {
  const browser = await puppeteer.launch();
  const [page] = await browser.pages();
  await page.setCookie(...myCookies);
  await page.goto('http://localhost:1234/testing');
  await page.screenshot({path: 'scratch_shot.png'});
  await browser.close();
})();

也就是说,我不确定 cookie 是否是完成您想要做的事情的最简单或最好的方法。由于您正在服务 HTML,您可以静态地传递数据,公开一个单独的 API 路由来收集前端可以使用的客户数据,或者传递 GET 参数,具体取决于数据的性质以及您最终要实现的目标。

您甚至可以在 React 应用程序上有一个文件上传表单,然后让 Puppeteer 通过该表单以编程方式将 JSON 数据上传到应用程序中。

事实上,如果您的最终目标是动态生成 PDF,那么使用 React 和 Puppeteer 可能有点矫枉过正,但我​​不确定在没有对您的用例进行一些研究和其他上下文的情况下我是否有更好的解决方案.