n.s*_*ish 4 pdf-generation google-chrome-headless puppeteer
我正在通过网页创建PDF。
我正在处理的应用程序是单页应用程序。
我在https://github.com/GoogleChrome/puppeteer/issues/1412上尝试了许多选项和建议
但这不起作用
const browser = await puppeteer.launch({
executablePath: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
ignoreHTTPSErrors: true,
headless: true,
devtools: false,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.goto(fullUrl, {
waitUntil: 'networkidle2'
});
await page.type('#username', 'scott');
await page.type('#password', 'tiger');
await page.click('#Login_Button');
await page.waitFor(2000);
await page.pdf({
path: outputFileName,
displayHeaderFooter: true,
headerTemplate: '',
footerTemplate: '',
printBackground: true,
format: 'A4'
});
Run Code Online (Sandbox Code Playgroud)
我要的是在页面完全加载后立即生成PDF报告。
我不想写任何类型的延迟,即await page.waitFor(2000);
我无法使用waitForSelector,因为该页面具有在计算后呈现的图表。
帮助将不胜感激。
Ana*_*jan 47
有时,networkidle事件并不总是表明页面已完全加载。仍然可能有一些JS scripts修改页面上的内容。因此,观察HTML浏览器对源代码修改的完成似乎会产生更好的结果。这是您可以使用的功能-
const waitTillHTMLRendered = async (page, timeout = 30000) => {
const checkDurationMsecs = 1000;
const maxChecks = timeout / checkDurationMsecs;
let lastHTMLSize = 0;
let checkCounts = 1;
let countStableSizeIterations = 0;
const minStableSizeIterations = 3;
while(checkCounts++ <= maxChecks){
let html = await page.content();
let currentHTMLSize = html.length;
let bodyHTMLSize = await page.evaluate(() => document.body.innerHTML.length);
console.log('last: ', lastHTMLSize, ' <> curr: ', currentHTMLSize, " body html size: ", bodyHTMLSize);
if(lastHTMLSize != 0 && currentHTMLSize == lastHTMLSize)
countStableSizeIterations++;
else
countStableSizeIterations = 0; //reset the counter
if(countStableSizeIterations >= minStableSizeIterations) {
console.log("Page rendered fully..");
break;
}
lastHTMLSize = currentHTMLSize;
await page.waitFor(checkDurationMsecs);
}
};
Run Code Online (Sandbox Code Playgroud)
您可以在页面load/click函数调用之后和处理页面内容之前使用它。例如
await page.goto(url, {'timeout': 10000, 'waitUntil':'load'});
await waitTillHTMLRendered(page)
const data = await page.content()
Run Code Online (Sandbox Code Playgroud)
Edu*_*nte 33
在某些情况下,对我来说最好的解决方案是:
await page.goto(url, { waitUntil: 'domcontentloaded' });
Run Code Online (Sandbox Code Playgroud)
您可以尝试的其他一些选项是:
await page.goto(url, { waitUntil: 'load' });
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.goto(url, { waitUntil: 'networkidle0' });
await page.goto(url, { waitUntil: 'networkidle2' });
Run Code Online (Sandbox Code Playgroud)
您可以在 puppeteer 文档中查看:https ://pptr.dev/# ? product = Puppeteer & version = v2.1.1 & show = api-pagewaitfornavigationoptions
Gra*_*ler 14
您可以用来page.waitForNavigation()在生成PDF之前等待新页面完全加载:
await page.goto(fullUrl, {
waitUntil: 'networkidle0',
});
await page.type('#username', 'scott');
await page.type('#password', 'tiger');
await page.click('#Login_Button');
await page.waitForNavigation({
waitUntil: 'networkidle0',
});
await page.pdf({
path: outputFileName,
displayHeaderFooter: true,
headerTemplate: '',
footerTemplate: '',
printBackground: true,
format: 'A4',
});
Run Code Online (Sandbox Code Playgroud)
如果有某些动态生成的元素想要包含在PDF中,请考虑使用page.waitForSelector()来确保内容可见:
await page.waitForSelector('#example', {
visible: true,
});
Run Code Online (Sandbox Code Playgroud)
将page.clickand包裹page.waitForNavigation在 Promise.all 中
await Promise.all([
page.click('#submit_button'),
page.waitForNavigation({ waitUntil: 'networkidle0' })
]);
Run Code Online (Sandbox Code Playgroud)
在最新的 Puppeteer 版本中,networkidle2对我有用:
await page.goto(url, { waitUntil: 'networkidle2' });
Run Code Online (Sandbox Code Playgroud)
小智 6
我总是喜欢等待选择器,因为它们中的许多是页面已完全加载的一个很好的指示:
await page.waitForSelector('#blue-button');
Run Code Online (Sandbox Code Playgroud)
您还可以使用来确保所有元素都已呈现
await page.waitFor('*')
Run Code Online (Sandbox Code Playgroud)
参考:https : //github.com/puppeteer/puppeteer/issues/1875
networkidle当我使用离屏渲染器时,我遇到了同样的问题。我需要一个基于 WebGL 的引擎来完成渲染,然后才制作屏幕截图。对我有用的是page.waitForFunction()方法。就我而言,用法如下:
await page.goto(url);
await page.waitForFunction("renderingCompleted === true")
const imageBuffer = await page.screenshot({});
Run Code Online (Sandbox Code Playgroud)
renderingCompleted在渲染代码中,完成后我只是将变量设置为 true。如果您无权访问页面代码,您可以使用其他一些现有标识符。
到目前为止,答案还没有提到一个关键事实:不可能编写一个适用于waitUntilPageLoaded每个页面的通用函数。如果可以的话,Puppeteer一定会提供的。
这样的函数不能依赖超时,因为总有一些页面的加载时间比该超时更长。当您延长超时以降低故障率时,在处理快速页面时会引入不必要的延迟。超时通常是一个糟糕的解决方案,选择退出 Puppeteer 的事件驱动模型。
如果响应涉及长时间运行的 DOM 更新(需要超过 500 毫秒才能触发渲染),则等待空闲网络请求可能并不总是有效。
等待 DOM 停止更改可能会错过缓慢的网络请求、长时间延迟的 JS 触发器或正在进行的 DOM 操作,这些操作可能会导致侦听器永远无法稳定下来,除非经过特殊处理。
当然,还有用户交互:验证码、提示和 cookie/订阅模式,需要在页面处于全页屏幕截图的合理状态之前单击并关闭(例如)。
由于每个页面都有不同的、任意的 JS 行为,因此典型的方法是编写适用于特定页面的事件驱动逻辑。做出精确、有针对性的假设比拼凑大量试图解决每种边缘情况的黑客要好得多。
如果您的用例是编写一个适用于每个页面的加载事件,我的建议是使用此处描述的工具的某种组合,最平衡地满足您的需求(速度与准确性,开发时间/代码复杂性与准确性, ETC)。对所有内容都使用故障保险,而不是盲目地假设所有页面都会符合您的假设。认真思考您真正需要尝试处理每个网页的程度。准备好妥协并接受一定程度的你可以忍受的失败。
以下是您可以混合搭配以等待负载满足您的需求的策略的快速概述:
page.goto()并page.waitForNavigation()默认为该load事件,“当整个页面加载时触发,包括所有依赖资源,例如样式表和图像”(MDN),但这通常过于悲观;无需等待大量您不关心的数据。通常数据无需等待所有外部资源即可获得,因此domcontentloaded应该更快。请参阅我的文章《避免木偶操纵者反模式》以进行进一步讨论。
另一方面,如果在 后有 JS 触发的网络请求load,您将错过该数据。因此networkidle2和networkidle0,在活动网络请求数为 2 或 0 后等待 500 毫秒。2 版本的动机是某些站点保持正在进行的请求打开,这会导致networkidle0超时。
如果您正在等待可能具有有效负载的特定网络响应(或者,对于一般情况,实现您自己的网络空闲监视器),请使用page.waitForResponse(). page.waitForRequest(),page.waitForNetworkIdle()并且page.on("request", ...)在这里也很有用。
如果您正在等待特定选择器可见,请使用page.waitForSelector()。如果您正在等待特定页面上的加载,请确定一个指示您要等待的状态的选择器。一般来说,对于特定于某一页面的脚本,这是等待您想要的状态的主要工具,无论您是提取数据还是单击某些内容。框架和影子根阻碍了这一功能。
page.waitForFunction()让您等待任意谓词,例如,检查页面的 HTML 或特定列表是否达到一定长度。它对于快速深入框架和影子根以等待依赖于嵌套状态的谓词也很有用。这个函数对于检测 DOM 突变也很方便。
最通用的工具是page.evaluate(),它将代码插入浏览器。您可以在这里输入任何您想要的条件;大多数其他 Puppeteer 函数都是常见情况的便捷包装器,您可以手动实现evaluate。
另请参阅页面何时加载?在 Playwright 文档中(是的,它不是 Puppeteer,但该工具非常相似,并且文档的这一部分涉及这两个库):
现代页面在加载事件被触发后执行许多活动。它们在加载事件触发后延迟获取数据、填充 UI、加载昂贵的资源、脚本和样式。没有办法判断页面是否已加载,这取决于页面、框架等。