我如何使用 Puppeteer 计算页面完全加载?

How I can calculate page fully load with Pupppeteer?

我正在尝试使用 Node 中的 puppeteer 以秒为单位获取页面完全加载时间,为此我对 API 和其他问题进行了一些研究并创建了以下代码:

/* First Configuration */
    puppeteer.launch({
            defaultViewport: { width: 1600, height: 800 }
      }).then(async browser => {
        const page = await browser.newPage();
    await page.setCacheEnabled(false);
        await page.goto('https://whosebug.com', {waitUntil: 'networkidle0'});

        /* Get Page Metrics */

        const perf = await page.metrics();
        console.log(JSON.stringify(perf));

        /* Get Page Evaluate */

        const timing = await page.evaluate(() => {
            const result = {};
            for (const key of Object.keys(window.performance.timing.__proto__))
                result[key] = window.performance.timing[key];
            return result;
        });
        console.log(JSON.stringify(timing));

        /* Show Results on Browser Close */

        await browser.close().then(() => {

    var fullyLoadEvaluate = (timing.loadEventEnd - timing.navigationStart);
        console.log('Fully Load Time (Page Evaluate): ' + fullyLoadEvaluate);

        var fullyLoadMetrics = (perf.LayoutDuration + perf.RecalcStyleDuration + perf.ScriptDuration + perf.TaskDuration);
        console.log('Fully Load Time (Page Metrics): ' + fullyLoadMetrics);

        /* Send Response to Server */
        res.send('Check The Console');
        });

      });

基本上我使用两个代码来return指标,其中一个是page.metrics()即return以下数据:

{"Timestamp":961736.600171,"Documents":8,"Frames":4,"JSEventListeners":375,"Nodes":8654,"LayoutCount":27,"RecalcStyleCount":31,"LayoutDuration":0.705517,"RecalcStyleDuration":0.144379,"ScriptDuration":0.527385,"TaskDuration":1.812213,"JSHeapUsedSize":11082496,"JSHeapTotalSize":20344832}

和最后一个page.evaluate()、return以下:

{"navigationStart":1556722407938,"unloadEventStart":0,"unloadEventEnd":0,"redirectStart":0,"redirectEnd":0,"fetchStart":1556722407938,"domainLookupStart":1556722408247,"domainLookupEnd":1556722408548,"connectStart":1556722408548,"connectEnd":1556722408737,"secureConnectionStart":1556722408574,"requestStart":1556722408738,"responseStart":1556722408940,"responseEnd":1556722409087,"domLoading":1556722408957,"domInteractive":1556722409995,"domContentLoadedEventStart":1556722409995,"domContentLoadedEventEnd":1556722410190,"domComplete":1556722412584,"loadEventStart":1556722412584,"loadEventEnd":1556722412589,"toJSON":{}}

在我的示例中,我正在测试站点 https://whosebug.com. Like webpagetest.org and getmetrix.com,我试图获得 页面完全加载时间

我知道这种值不一致,但我想知道我计算的值是否正确,这两个结果中哪个更正确? Fully Load Time (Page Evaluate)Fully Load Time (Page Metrics) ?

您可以使用page.metrics()来比较两个时间点(例如page.goto之前和之后)。从 performance API 中读取数据的 page.evaluate 方法也是一个不错的选择。正如我已经在评论中指出的那样,没有定义什么应该被视为 "full page load"。两种方法都有效。

更复杂

人们可能认为要加载的页面有很多:

  • DOMContentLoaded 事件触发
  • Load 事件触发
  • 从导航开始到文档中嵌入的所有资源(如加载图像)所花费的时间
  • 从导航开始到加载所有资源所花费的时间
  • 没有更多正在进行的网络请求之前的时间。
  • ...

您还必须考虑是否希望网络相关阶段(如 DNS)成为测量的一部分。这是一个示例请求(使用 Chrome DevTools Network 选项卡生成)显示单个请求可能有多复杂:

还有一个 document 解释每个阶段。

简单的方法

测量加载时间最简单的方法就是在导航开始时开始测量,并在页面加载后停止测量。可以这样做:

const t1 = Date.now();
await page.goto('https://example.com');
const diff1 = Date.now() - t1;
console.log(`Time: ${diff1}ms`);

请注意,还有其他 API(page.metricsprocess.hrtime, perf_hooks)以获得更精确的时间戳。

您还可以将选项传递给 page.goto 函数,以将承诺的解析更改为如下内容(引用自文档):

Consider navigation to be finished when there are no more than 0 network connections for at least 500ms

为此,您必须使用设置 networkidle0:

await page.goto('https://example.com', { waitUntil: 'networkidle0' });

上面链接的文档中还有其他事件可供您使用。

更复杂:使用性能 API

要获得更精确的结果,您可以像这样使用 Performance API as you already did in your code. Instead of going through the prototype of window.performance you can also use the functions performance.getEntries() or performance.toJSON()

const perfData = await page.evaluate(() =>
    JSON.stringify(performance.toJSON(), null, 2)
);

这样,您将获得如下所示的数据:

{
  "timeOrigin": 1556727036740.113,
  "timing": {
    "navigationStart": 1556727036740,
    "unloadEventStart": 0,
    "unloadEventEnd": 0,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 1556727037227,
    "domainLookupStart": 1556727037230,
    "domainLookupEnd": 1556727037280,
    "connectStart": 1556727037280,
    "connectEnd": 1556727037348,
    "secureConnectionStart": 1556727037295,
    "requestStart": 1556727037349,
    "responseStart": 1556727037548,
    "responseEnd": 1556727037805,
    "domLoading": 1556727037566,
    "domInteractive": 1556727038555,
    "domContentLoadedEventStart": 1556727038555,
    "domContentLoadedEventEnd": 1556727038570,
    "domComplete": 1556727039073,
    "loadEventStart": 1556727039073,
    "loadEventEnd": 1556727039085
  },
  "navigation": {
    "type": 0,
    "redirectCount": 0
  }
}

因此,如果您想知道从 navigationStartloadEventStart 花了多长时间,您可以从另一个值中减去一个值(例如 1556727039073 - 1556727036740 = 2333 毫秒)。

那么选择哪一个?

这取决于您的决定。通常,使用 Load 事件作为起点是个好主意。等待所有请求完成可能实际上永远不会发生,因为后台不断加载资源。如果您不想使用加载事件,则将 networkidle2 用作 waitUntil option 可能是一种替代方法。

不过,最终还是要看您的用例要使用哪个指标。