pagespeed insights api 怎么用:一份 JSON 里有两套数据,其中一套正在搬走

pagespeed insights api 一次请求返回两套互不相干的数:真实 Chrome 用户量出来的实测数据,和 Lighthouse 跑一次的实验室数据。这篇讲清哪一块是哪个、两个最容易漏的参数,以及实测那一半正在搬去哪个端点。

效果衡量8 分钟读完2170 次阅读
pagespeed insights api 怎么用:一份 JSON 里有两套数据,其中一套正在搬走

pagespeed insights api 一次请求返回两套互不相干的数:一套是真实 Chrome 用户量出来的实测数据,另一套是 Lighthouse 在 Google 自己机器上跑一次的实验室数据。前者回答「访客实际经历了什么」,后者回答「一次合成加载打了多少分」。两套数各管各的问题,其中只有一套适合按周对比。而实测那一半,Google 已经准备把它从这个 API 里搬走。

读这篇前

你需要一个自己控制的网址和一个终端。打几次调用不需要 Google 账号,但只要你打算把它挂上定时任务,就得有一个 API key —— 不带 key 的那个池子是共享的,会耗尽。

这一章是一对里的后半篇。如果你还没从 Search Console 里用程序取过数,先读 Search Console API 怎么用:OAuth 和 API key 该怎么选、Google 的接口返回长什么样,那篇都讲了,这一章假设你已经知道。如果你真正想弄明白的是「页面慢到底让我损失了什么」,那是另一个问题,先看 core web vitals 值不值得做更划算。

pagespeed insights api 到底返回什么

一个端点、一个必填参数、三个有名字的返回块。端点是 GET https://www.googleapis.com/pagespeedonline/v5/runPagespeed,唯一必填的参数是 url,这个方法不带请求体。

返回块装的是什么来源能跨时间比吗
loadingExperience你问的那个网址的实测指标CrUX,真实 Chrome 用户能,但慢 —— 它是滚动窗口
originLoadingExperience整个源站的实测指标CrUX,真实 Chrome 用户能 —— 单条网址流量太小时用这个
lighthouseResult实验室那一次跑的分数、审计项、耗时Lighthouse,一次合成加载只有 Lighthouse 版本没变时才能比

两个可选参数承担了大部分实际工作。strategydesktopmobile,默认是 desktop —— 这跟 PageSpeed Insights 网页上先给你看移动端正好相反,所以你不显式传它,脚本和网页截图一定对不上。categoryperformanceaccessibilitybest-practicesseo 四个之一,一个都不传时只跑 performance

为什么一次请求里有两套数

实测数据和实验室数据的采集方式根本没法调和,所以 Google 把两套都放进响应里,让你自己挑。实测数据来自 Chrome User Experience Report:它是过去 28 天里真实用户在真实设备上的页面加载聚合出来的,对外报的是第 75 百分位。实验室数据来自 Lighthouse:它在一台被限速的机器上把你的页面加载一次,然后逐项审计。一次合成加载可以精确复现;一个实测数字则完全无法复现,因为你没法把上个月的访客再请回来一次。

这个差别决定了哪套数该用在哪个位置。要写进报表、要按月对比的,是实测数据。要用来定位问题的,是实验室数据 —— 因为它能指到具体那一条审计:一张没写尺寸的图、一张阻塞渲染的样式表、一个到得太晚的字体。

有一件事必须直说,因为它会改变你该写什么代码。Google 自己的文档现在写着这一句:「We plan to discontinue including real-world data from the Chrome User Experience Report in this API. We recommend the CrUX API (guide) or the CrUX History API (guide) instead.」PageSpeed Insights 的实测那一半正在退场。你今天要写这个集成,就把实测那一半写成 CrUX API,把 PageSpeed Insights 留给实验室那一半。

按这个顺序做

四步。每一步都有一个动手前能验证的完成标志,第一次跑完大约十五分钟。

  1. 先不带 key 调一次,看状态码。一条裸 curl 打到端点就够。完成标志是拿到 JSON。如果拿到 429,说明你所在 IP 的无 key 池子已经用完,不先拿 key 什么都跑不起来。
  2. 申请一个 API key 并拼上去。在 Google Cloud 控制台建一个,把 key=… 加到请求地址后面。文档明确说这个 key「is safe for embedding in URLs; it doesn't need any encoding」。完成标志是同一个调用返回 200
  3. 先定你要哪一半,再调对应的 API。实验室数据走 PageSpeed Insights 的 runPagespeed;实测数据走 CrUX 的 POST https://chromeuxreport.googleapis.com/v1/records:queryRecord。完成标志是你手上有一个能说清名字的数 —— 比如「本源站在手机上的 LCP 第 75 百分位」。
  4. 把版本号一起存下来。每个实验室响应都带 lighthouseVersionfetchTime,两个都存。完成标志是上个月的任何一个数字,都能追回到产出它的那次工具构建。

第四步是大多数人跳过的,也正是它让剩下那堆数据变得可用。

交付物:一个脚本,两半都取

下面这个脚本吃一串网址,从 PageSpeed Insights 取实验室分数、从 CrUX 取实测指标,每个网址打一行。它是「还能说清每个数字出自哪里」的最小版本。

#!/usr/bin/env python3
# 两个端点、两套数据。实测是 28 天滚动窗口;实验室是一次合成加载。
import json, sys, urllib.request, urllib.parse

KEY = "你的 Google API KEY"          # 两个 API 用同一个 key
PSI = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"
CRUX = "https://chromeuxreport.googleapis.com/v1/records:queryRecord"

def get(url):
    with urllib.request.urlopen(url, timeout=90) as r:
        return json.loads(r.read())

def post(url, payload):
    req = urllib.request.Request(url, data=json.dumps(payload).encode(),
                                 method="POST", headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=90) as r:
        return json.loads(r.read())

def lab(url, strategy="mobile"):
    q = urllib.parse.urlencode({"url": url, "strategy": strategy, "key": KEY})
    d = get(f"{PSI}?{q}")
    lh = d.get("lighthouseResult", {})
    return lh.get("lighthouseVersion"), (lh.get("categories", {}).get("performance") or {}).get("score")

def field(origin):
    d = post(f"{CRUX}?key={KEY}", {"origin": origin, "formFactor": "PHONE",
                                   "metrics": ["largest_contentful_paint", "cumulative_layout_shift",
                                               "interaction_to_next_paint"]})
    out = {}
    for name, m in (d.get("record", {}).get("metrics") or {}).items():
        out[name] = m.get("percentiles", {}).get("p75")
    return out

if __name__ == "__main__":
    for u in sys.argv[1:]:
        origin = "{0.scheme}://{0.netloc}".format(urllib.parse.urlsplit(u))
        ver, score = lab(u)
        print(f"{u}\n  实验室  lighthouse {ver}  performance {score}")
        for name, p75 in field(origin).items():
            print(f"  实测    {name:26s} p75 {p75}")

脚本里有三处是刻意的。实验室那次显式传了 strategy=mobile,因为默认是 desktop,而网页默认给你看移动端。实测那次问的是源站而不是单条网址,因为一条网址经常因为流量太小而根本没进数据集。实测那次还按名字点名要三个指标,而不是拿到什么算什么 —— 这样某个指标被新增或退役时,你的报表列不会悄悄变掉。

有两张表值得贴在脚本旁边。第一张是指标表,因为五个指标里有一个不是毫秒、也没法求平均。「良好」那三档阈值出自 Google 自己对 Core Web Vitals 的定义,2026 年 9 月 21 日访问:LCP「should occur within 2.5 seconds」,INP「should have a INP of 200 milliseconds or less」,CLS 应维持在「0.1 or less」,且官方建议看第 75 百分位(web.dev/articles/vitals)。FCP 和 TTFB 同一个端点也返回,但它们不是 Core Web Vitals,没有官方阈值,我们也不打算编一个。

指标单位良好阈值报表口径
LCP毫秒≤ 2500第 75 百分位
INP毫秒≤ 200第 75 百分位
CLS无量纲≤ 0.1第 75 百分位
FCP毫秒非 CWV 指标第 75 百分位
TTFB毫秒非 CWV 指标第 75 百分位

第二张是两个 API 的分工对照表,也是第一次上手最容易选错的那个决定。

问题PageSpeed InsightsCrUX API
要不要 key不强制,但强烈建议必须
有实测数据吗有,但正在被移除有,它就是干这个的
有实验室数据吗没有
粒度单条网址与源站单条网址与源站
历史数据没有有,CrUX History API
更新节奏每次调用现跑一次每天更新,约 04:00 UTC

会踩的坑

拿两个不同 Lighthouse 版本的分数去比。这是这一行里最常见的一个坏数字。Lighthouse 的评分口径会带破坏性改动,这个 API 自己的 release notes 一条条记着 —— Lighthouse 10、11、12、13 落地时都有关于响应和分数变动的说明。今天 74 分、上季度 68 分,可能根本是同一个页面。解决办法不是不比较,而是把 lighthouseVersion 一起存下来,跨版本时拒绝画成一条折线。

把实验室分数当成用户的真实体验来汇报。一次实验室跑动是在一台机器上、一个固定限速下的一次加载。它是很好的调试仪器,是很差的报表。有人问「这个站到底有多快」,答案只能是实测数字,没有实测数字就只能说没有。

把实测那一半建在正在退场的端点上。文档说得很清楚:计划把 CrUX 数据从 PageSpeed Insights 里移除,并指向 CrUX API。今天照着 loadingExperience 写,等于给自己留一次迁移;照着 CrUX 写,实测那一半已经落到了它要去的地方。

常见问题

pagespeed insights api 需要 key 吗

不强制,但你迟早要一个。文档的原话是它「can be used with or without an API key, although a key is recommended for frequent, automated queries」。不带 key 时你用的是共享池。我们在 2026 年 9 月 21 日把池子用到底了,拿回来的是 429 和这句:「Quota exceeded for quota metric 'Queries' and limit 'Queries per day' of service 'pagespeedonline.googleapis.com'」。这正是为什么交付物第一步先看状态码,而不是先写循环。

脚本跑出来的分和网页上看到的不一样,为什么

九成是 strategy。API 默认 desktop,网页默认打开移动端。显式传 strategy=mobile,两边就一致了。如果还差,看一眼网页上是不是一个已保存的报告 —— 它的分享链接会把结果快照下来,最多保留 30 天。

流量很小的页面能拿到 Core Web Vitals 吗

通常拿不到,这是数据的性质,不是 bug。实测数据集里只有达到最小真实访问量的网址,所以一个没什么人访问的页面根本不会出现。要么改问源站,要么接受「没人访问的页面没有实测数据可报」这件事。

PageSpeed Insights 的实测数据和 CrUX API 是同一份吗

底层是同一份数据集,交付方式不同。我们没有把两边并排测过,也不会声称它们每次都精确到小数位一致。有文档支撑的是:PageSpeed Insights 返回的实测数据来自 Chrome User Experience Report,而 Google 计划不再在那里提供它,并建议改用 CrUX API。

挂定时任务时怎么才不把配额打爆

把响应缓存下来,需要的时候才重跑某个网址。实验室数据只在页面变了的时候才会变,实测数据一天才更新一次。几百个网址跑一晚一次没问题,五分钟跑一次不行 —— 配额会用同一个 429 告诉你。

有一条边界要说明白:这一章只讲怎么把数取出来,不讲拿到数之后怎么办。性能分不能告诉你这个页面赚不赚钱,我们也没有测过这两套数据与排名之间的关系。要往那边想,诚实的起点是 同一个首页连测三次差了 542 毫秒那篇实测;而把一次测量变成一个真能发布出去的动作,是 QueryWin 在做的事

本文属于 QueryWin 实操手册 · 第 2 阶

pagespeed insights api 怎么用:一份 JSON 里有两套数据,其中一套正在搬走