Я работаю над созданием 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'
});
Я хочу создать отчет в формате PDF, как только страница будет полностью загружена.
Я не хочу писать никаких задержек, например, await page.waitFor (2000);
Я не могу выполнить waitForSelector, потому что на странице есть диаграммы и графики, которые отображаются после вычислений.
Помощь будет оценена.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Мне всегда нравится ждать селекторы, так как многие из них являются отличным индикатором того, что страница полностью загружена:
await page.waitForSelector('#blue-button');
@ Arch4Arts вы должны создать свою собственную функцию щелчка, которая будет ждать вас, а также щелкнуть
Вы можете использовать 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',
});
Если есть определенный динамически генерируемый элемент, который вы хотели бы включить в свой PDF-файл, рассмотрите возможность использования page.waitForSelector(), чтобы гарантировать, что содержимое будет видимым:
await page.waitForSelector('#example', {
visible: true,
});
Где документация для сигнала networkidle0?
'networkidle0' задокументирован здесь github.com/GoogleChrome/puppeteer/blob/master/docs/…
Должен ли page.waitForSelector вызываться после page.goto или раньше? Не могли бы вы ответить на аналогичный вопрос, который я задал stackoverflow.com/questions/58909236/…?
Зачем мне использовать networkidle0, если я могу использовать событие загрузки по умолчанию? Это быстрее использовать networkidle0?
Оберните page.click и page.waitForNavigation в Promise.all
await Promise.all([
page.click('#submit_button'),
page.waitForNavigation({ waitUntil: 'networkidle0' })
]);
В некоторых случаях лучшим решением для меня было:
await page.goto(url, { waitUntil: 'domcontentloaded' });
Вот некоторые другие варианты, которые вы можете попробовать:
await page.goto(url, { waitUntil: 'load' });
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.goto(url, { waitUntil: 'networkidle0' });
await page.goto(url, { waitUntil: 'networkidle2' });
Вы можете проверить это в документации кукольника: https://pptr.dev/#?product=Puppeteer&version=v2.1.1&show=api-pagewaitfornavigationoptions
Это не гарантирует, что все загруженные скрипты завершат выполнение. Следовательно, HTML все еще может отображаться, и это будет продолжаться.
В последней версии Puppeteer у меня работал networkidle2:
await page.goto(url, { waitUntil: 'networkidle2' });
Иногда события networkidle не всегда указывают на то, что страница полностью загружена. Все еще может быть несколько JSscripts, изменяющих содержимое на странице. Таким образом, наблюдение за завершением изменений исходного кода 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.info('last: ', lastHTMLSize, ' <> curr: ', currentHTMLSize, " body html size: ", bodyHTMLSize);
if (lastHTMLSize != 0 && currentHTMLSize == lastHTMLSize)
countStableSizeIterations++;
else
countStableSizeIterations = 0; //reset the counter
if (countStableSizeIterations >= minStableSizeIterations) {
console.info("Page rendered fully..");
break;
}
lastHTMLSize = currentHTMLSize;
await page.waitFor(checkDurationMsecs);
}
};
Вы можете использовать это после вызова функции страницы load / click и перед обработкой содержимого страницы. например
await page.goto(url, {'timeout': 10000, 'waitUntil':'load'});
await waitTillHTMLRendered(page)
const data = await page.content()
Я не уверен, почему этот ответ не получил больше «любви». На самом деле, в большинстве случаев нам просто нужно убедиться, что JavaScript не вмешивается в страницу, прежде чем мы ее очистим. Сетевые события этого не позволяют, и если у вас есть динамически генерируемый контент, не всегда есть что-то, что вы можете надежно выполнить с помощью "waitForSelector / visible: true" на
Спасибо @roberto - кстати, я только что обновил ответ, вы можете использовать его с событием load, а не networkidle2. Думал, с этим будет немного оптимальнее. Я протестировал это на производстве и могу подтвердить, что он тоже хорошо работает!
это отличное решение. Спасибо, что поделились!
Отличное решение, которое должно быть частью библиотеки puppeteer, однако, пожалуйста, не ждите, потому что эта функция устарела и будет удалена в следующем выпуске: github.com/puppeteer/puppeteer/issues/6214
Я попытался установить checkDurationMsecs на 200 мс, и bodyHTMLSize продолжает меняться и давать огромные числа, я также использую электрон и прямоугольник, очень странно.
Хорошо, я нашел эту нелепую ошибку, которую трудно поймать. Если вам удастся поймать эту страницу html длиной 100k, вы поймете, что существуют классы CSS, такие как CodeMirror, должны быть codemirror.net, что означает .... document.body.innerHTML также ловит консоль разработчика! Просто удалите mainWindow.webContents.openDevTools(); для тестирования e2e. Надеюсь, неприятных сюрпризов больше не будет.
Решил мою головную боль при подключении с высокой задержкой .. Хорошо, сделано
Отличное решение !!! Это должен быть принятый ответ.
Я пробовал несколько решений, это единственное, что действительно сработало полностью. Спасибо, @AnandMahajan
Вы также можете использовать, чтобы убедиться, что все элементы отрисованы
await page.waitFor('*')
Ссылка: https://github.com/puppeteer/puppeteer/issues/1875
waitFor устарел и будет удален в следующем выпуске. См. github.com/puppeteer/puppeteer/issues/6214 для подробностей и того, как перенести ваш код.
Что касается декабря 2020 года, функция waitFor устарела, поскольку предупреждение внутри кода сообщает:
waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.
Вы можете использовать:
sleep(millisecondsCount) {
if (!millisecondsCount) {
return;
}
return new Promise(resolve => setTimeout(resolve, millisecondsCount)).catch();
}
И используйте это:
(async () => {
await sleep(1000);
})();
просто используйте page.waitForTimeout (1000)
Проверим. Спасибо.
В выпуске github говорится, что они просто устарели "волшебной" функции waitFor. Вы по-прежнему можете использовать одну из специальных функций waitFor * (). Следовательно, ваш код sleep () не нужен. (Не говоря уже о том, что он слишком сложен для того, что он делает, и, как правило, решать проблемы параллелизма с помощью программных тайм-аутов - плохая идея.)
Я столкнулся с той же проблемой с networkidle, когда работал над внеэкранным рендерером. Мне нужен был движок на основе WebGL, чтобы закончить рендеринг и только потом сделать снимок экрана. Для меня сработал метод page.waitForFunction (). В моем случае использование было следующим:
await page.goto(url);
await page.waitForFunction("renderingCompleted === true")
const imageBuffer = await page.screenshot({});
В коде рендеринга я просто установил для переменной renderingCompleted значение true, когда закончил. Если у вас нет доступа к коду страницы, вы можете использовать другой существующий идентификатор.
Вы гений, это такое очевидное решение, особенно когда вы ждете конкретных элементов, и как только я сам не догадался, спасибо!