Обнаружение событий рендеринга / изменений макета (или любой способ узнать, когда страница перестала "меняться")

Я использую 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. Когда изменения прекратились в течение определенного периода времени, вы можете возобновить свою программу.

Другие вопросы по тегам