Обнаружение событий рендеринга / изменений макета (или любой способ узнать, когда страница перестала "меняться")
Я использую Puppeteer (на самом деле PuppeteerSharp, но API тот же), чтобы сделать скриншот веб-страницы из моего приложения.
Проблема заключается в том, что страница выполняет несколько изменений макета с помощью JavaScript после загрузки страницы, поэтому проходит несколько секунд, прежде чем вы увидите "окончательную" визуализированную версию страницы.
В данный момент я просто жду "безопасного" количества секунд, прежде чем делать снимок экрана, но это, очевидно, не очень хороший подход, так как временное снижение производительности на машине может привести к неполному рендерингу.
Поскольку кукловод использует Chromium в фоновом режиме, есть ли способ перехватывать события макета / рендеринга Chromium (как вы можете это сделать в консоли DevTools в Chrome)? Или, действительно, ЛЮБОЙ другой способ узнать, когда страница перестала "меняться" (визуально я имею в виду)
РЕДАКТИРОВАТЬ, еще немного информации: контент динамический, поэтому я не знаю заранее, что он будет рисовать и как. По сути, это фреймворк, который рисует различные диаграммы / таблицы / изображения / и т.д. (не с открытым исходным кодом, к сожалению). Однако, протестировав с помощью инструмента "производительность" в Chrome DevTools, я заметил, что после завершения рендеринга страницы все действия на временной шкале прекращаются, поэтому, если бы я мог получить доступ к этой информации, было бы здорово. К сожалению, единственный способ сделать это в Puppeteer (который я вижу) - использовать функцию "Tracing", но она не работает в режиме реального времени. Вместо этого он создает дамп трассировки в файл, и буфер слишком велик, чтобы его можно было использовать (файл по-прежнему равен 0 байтам после того, как моя страница уже завершила рендеринг, он сбрасывается на диск только тогда, когда я вызываю "stopTracing"). Что мне нужно, так это получить доступ к функции трассировки кукловода в режиме реального времени, например, через события или поток в памяти, но, похоже, это не поддерживается API. Есть ли способ обойти это?
2 ответа
После некоторых проб и ошибок я остановился на этом решении:
string traceFile = IOHelper.GetTemporaryFile("txt");
long lastSize = 0;
int cyclesWithoutTraceActivity = 0;
int totalCycles = 0;
while (cyclesWithoutTraceActivity < 4 && totalCycles < 25)
{
File.Create(traceFile).Close();
await page.Tracing.StartAsync(new TracingOptions()
{
Categories = new List<string>() { "devtools.timeline" },
Path = traceFile,
});
Thread.Sleep(500);
await page.Tracing.StopAsync();
long curSize = new FileInfo(traceFile).Length;
if(Math.Abs(lastSize - curSize) > 5)
{
logger.Debug("Trace activity detected, waiting...");
cyclesWithoutTraceActivity = 0;
}
else
{
logger.Debug("No trace activity detected, increasing idle counter...");
cyclesWithoutTraceActivity++;
}
lastSize = curSize;
totalCycles++;
}
File.Delete(traceFile);
if(totalCycles == 25)
{
logger.Warn($"WARNING: page did not stabilize within allotted time limit (15 seconds). Rendering page in current state, might be incomplete");
}
По сути, я делаю вот что: трассировку Chromium я запускаю с интервалами 500 мсек, и каждый раз сравниваю размер последнего файла трассировки с размером текущего файла трассировки. Любые существенные изменения в размере интерпретируются как активность на временной шкале, и они сбрасывают счетчик простоя. Если прошло достаточно времени без существенных изменений, я предполагаю, что страница закончила рендеринг. Обратите внимание, что файл трассировки всегда начинается с некоторой отладочной информации (даже если сама временная шкала не имеет активности для отчета), поэтому я не делаю точного сравнения размеров, а вместо этого проверяю, больше ли длины файла 5 байт друг от друга: поскольку начальная отладочная информация содержит некоторые счетчики и идентификаторы, которые меняются со временем, я допускаю небольшую разницу для объяснения этого.
Вы должны использовать page.waitForSelector()
ждать, пока динамические элементы завершат рендеринг.
Должен быть шаблон, который можно идентифицировать с точки зрения генерируемого контента.
Помните, что вы можете использовать гибкие CSS-селекторы для сопоставления элементов или атрибутов, не зная их точных значений.
await page.goto( 'https://example.com/', { 'waitUntil' : 'networkidle0' } );
await Promise.all([
page.waitForSelector( '[class^="chart-"]' ), // Class begins with 'chart-'
page.waitForSelector( '[name$="-image"]' ), // Name ends with '-image'
page.waitForSelector( 'table:nth-of-type(5)' ) // Fifth table
]);
Это может быть полезно при ожидании существования определенного шаблона в DOM.
Если page.waitForSelector()
не достаточно мощный, чтобы удовлетворить ваши потребности, вы можете использовать page.waitForXPath()
:
await page.waitForXPath( '//div[contains(text(), "complete")]' ); // Div contains 'complete'
Кроме того, вы можете подключить MutationObserver
интерфейс в page.evaluate()
следить за изменениями, вносимыми в дерево DOM. Когда изменения прекратились в течение определенного периода времени, вы можете возобновить свою программу.