从 Node 批量运行 Lighthouse 审计数百个页面(无需 PageSpeed API 密钥),以及分数为何会波动

来源:devto:articles · 原作者 SwiftKit · 原文
系统转载,非作者本人发布。

PageSpeed Insights 非常适合单个 URL。但面对 300 个产品页,或者“每次发布后所有落地页”这样的场景,你需要的是在脚本里运行 Lighthouse。好消息是:Lighthouse 是一个 npm 包,你不需要 API 密钥就能自己运行它。

最小脚本

import lighthouse from 'lighthouse';
import * as chromeLauncher from 'chrome-launcher';

const chrome = await chromeLauncher.launch({
  chromeFlags: ['--headless=new', '--no-sandbox', '--disable-dev-shm-usage'],
});
try {
  const { lhr, report } = await lighthouse('https://www.wikipedia.org', {
    port: chrome.port,
    output: ['json', 'html'],
    onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'],
    logLevel: 'error',
  });
  console.log(Math.round(lhr.categories.performance.score * 100));
  // report[1] is the full HTML report: save it, it's what people actually read.
} finally {
  await chrome.kill();
}

对于桌面预设,将 Lighthouse 自身的配置作为第三个参数传入:

const desktop = (await import('lighthouse/core/config/desktop-config.js')).default;
await lighthouse(url, flags, desktop);

我遇到的两个小坑:

  • 在 chrome-launcher 1.x 中,kill() 不保证返回 promise,所以 chrome.kill().catch(...) 可能会抛错。改用 try/catch 包裹它。
  • 如果你在 Docker 中使用 Playwright 镜像,将 chrome-launcher 指向 Playwright 的浏览器:chromePath: chromium.executablePath()。

从结果中保留什么

lhr 对象非常庞大。对于批量表格,我每页保留以下内容:

const score = (c) => Math.round(lhr.categories[c].score * 100);
const ms = (id) => Math.round(lhr.audits[id].numericValue);
const row = {
  performance: score('performance'),
  seo: score('seo'),
  lcpMs: ms('largest-contentful-paint'),
  tbtMs: ms('total-blocking-time'),
  cls: lhr.audits['cumulative-layout-shift'].numericValue,
  // Top fixes, ranked by time saved:
  opportunities: Object.values(lhr.audits)
    .filter((a) => a.details?.type === 'opportunity' && a.details.overallSavingsMs >= 100)
    .sort((a, b) => b.details.overallSavingsMs - a.details.overallSavingsMs)
    .slice(0, 8)
    .map((a) => ({ title: a.title, savingsMs: Math.round(a.details.overallSavingsMs) })),
};

在读取任何内容之前,也要检查 lhr.runtimeError。加载失败的页面仍会返回 lhr,只是分数是无效的。

一次只运行一个审计

同时审计 10 个页面很诱人。但别这样做:Lighthouse 会模拟慢速设备并测量 CPU 工作量,因此在同一台机器上并行审计会互相拖慢,并把每个性能分数都拉低。请逐个运行,每次都用全新的 Chrome。这样虽然更慢,但数字才有意义。

分数为何会波动

我在自己的笔记本和一个 2 GB 云容器上运行了相同的页面:

页面 笔记本(移动端) 云容器(移动端)
wikipedia.org 性能 98 92
wikipedia.org LCP 1,379 ms 1,497 ms

一个更重的营销网站在云端的性能得分仅为 25,LCP 超过 9 秒。同样的代码,同一天。

实验室分数取决于机器的 CPU,而移动端预设会在此基础上叠加 CPU 降速。因此:

  • 在同一台机器、同一次运行中比较页面,不要跨工具比较。
  • PageSpeed Insights 在 Google 拥有足够流量时也会显示真实用户数据(CrUX)。这是一种不同的测量,而且往往对 SEO 更关键。
  • 追踪趋势(相同页面、相同配置,每周一次),而不是单个数字。

打包好的版本

如果你不想自己运行 Chrome 集群,我把它放到了 Apify 上,名为 Lighthouse Audit:粘贴 URL,选择移动端、桌面端或两者,即可获得分数、Core Web Vitals、最重要的修复建议、失败的 SEO/无障碍检查,以及每个页面的完整 HTML 报告。出于上述原因,它一次只运行一个审计。失败的页面不收费。

你们目前是如何跨多个页面追踪性能的?我很好奇大家是使用 CrUX 数据、实验室运行,还是两者都用。


在 AI 辅助下撰写;代码和数字在发布前均已测试。

原站讨论节选 · Eusebiu Balan

原站发表于 2026-10-03T23:29:29.000Z
原站评论节选,非作者本人在本站回复。原评论

那个表格里也值得保留 lhr.environment.benchmarkIndex。Lighthouse 会在每次运行前测量机器的速度,因此当每周分数下降时,你可以判断是容器变慢了还是页面变慢了。

他们关于波动性的说明还建议每个 URL 运行多次并取中位数,这能平滑掉繁忙机器上一次偶发的糟糕运行。

原站讨论节选 · SwiftKit

原站发表于 2026-10-04T02:04:36.000Z · 回复原评论 3gd38
原站评论节选,非作者本人在本站回复。原评论

谢谢,这是个很好的建议。我已经把这两点都加到了工具里:每条结果现在都包含 benchmarkIndex,并且有一个“每页运行次数”选项(最多 5 次),会保留性能分数为中位数的那次运行,同时列出所有分数和每次运行的 benchmarkIndex。在一次快速测试中,三次运行分别得到 97、100 和 100,benchmark 指数分别为 3963、3834 和 4115,这很好地说明了机器本身在运行之间变化有多大。