检测呈现事件/布局更改(或任何了解页面何时停止的方式 "changing")

Detecting rendering events / layout changes (or any way to know when the page has stopped "changing")

我正在使用 Puppeteer(实际上是 PuppeteerSharp,但 API 是相同的)从我的应用程序截取网页。

问题是页面在加载后通过 JavaScript 进行了几次布局更改,因此在看到页面的 "final" 呈现版本之前过了几秒钟。

目前我只是在等待 "safe" 秒后才截取屏幕截图,但这显然不是一个好方法,因为机器性能暂时下降会导致渲染不完整.

由于 puppeteer 在后台使用 Chromium,是否有办法拦截 Chromium 的 layouting/rendering 事件(就像您在 Chrome 中的 DevTools 控制台中所做的那样)?或者,实际上,任何其他方式都可以知道页面何时停止 "changing"(我的意思是视觉上)

编辑,更多信息:内容是动态的,所以我事先不知道它将绘制什么以及如何绘制。基本上,它是一个绘制不同charts/tables/images/etc的框架。 (不幸的是不是开源的)。然而,通过使用 Chrome DevTools 中的 "performance" 工具进行测试,我注意到在页面完成渲染后,时间轴中的所有 activity 都停止了,所以如果我可以访问该信息,它将是伟大的。不幸的是,在 Puppeteer 中(我可以看到)做到这一点的唯一方法是使用 "Tracing" 功能,但这不是实时运行的。相反,它将跟踪转储到文件中,缓冲区太大而无法使用(在我的页面完成渲染后文件仍然是 0 字节,它只在我调用 "stopTracing" 时刷新到磁盘)。我需要的是实时访问 puppeteer 的跟踪功能,例如通过事件或内存流,但这似乎不受 API 支持。有什么解决办法吗?

您应该使用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 interface into page.evaluate() 以观察对 DOM 树所做的更改。当更改停止一段时间后,您可以恢复程序。

经过反复试验,我选择了这个解决方案:

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 毫秒的间隔进行跟踪,每次我将上一个跟踪文件的大小与当前跟踪文件的大小进行比较。大小的任何重大变化在时间轴上都被解释为 activity,并且它们会重置空闲计数器。如果经过足够长的时间而没有发生重大变化,我认为页面已完成渲染。请注意,跟踪文件总是以一些调试信息开头(即使时间线本身没有 activity 报告),这就是为什么我不进行精确大小比较的原因,而是检查文件的长度相隔超过 5 个字节:由于初始调试信息包含一些随时间变化的计数器和 ID,因此我允许有一点差异来解决这个问题。