1319 字
7 分钟

同一个站的两套统计,为什么差了 2.7 倍

本站装着两套统计:Umami Cloud 与自建的 Worker + D1。同一时间窗内,首页一个记 67 次、一个记 180 次。我做了个实验证明主因是广告拦截器,并列出另外三种会放大差距的机制,以及最后决定给读者看哪个数字。
10月24日 19:20

本站装着两套访问统计,而且都是我自己在用的:

  1. Umami Cloud —— 从 2025 年就在记,跨域名迁移后历史保留;
  2. 自建 Worker + D1 —— 一个 tracker.js 加一张 pageviews 表(pathname 主键 + views)。

按理说它们看的是同一批访客。但把时间窗对齐到「迁移到 peroe.cn 之后」,同一段时间里首页被记了 67 次和 180 次。

同一时间窗的两套统计:首页 67 vs 180,全站 267 vs 294

先对齐口径,再谈差异#

不同口径比大小没有意义,所以我做了三件事:

  1. 同一个时间窗:都从 2026-10-06(迁到新域那天)开始算 —— Umami 里还留着旧站 blog.peroe.top 的历史,不截断的话它当然更大;
  2. 同一个指标:都取 pageview(页面浏览次数),不拿 visitors / visits 去比;
  3. 同一个站点:都只看 blog.peroe.cn 下的路径。

对齐之后,全站是 267 对 294 —— 差 10%,还算接近。但单个页面差得离谱:首页 67 vs 180(2.7 倍)、/posts/hello-world/ 1 vs 7(7 倍)。

实验:把 Umami 的域名挡掉#

第一个怀疑对象是广告拦截器。cloud.umami.is 是个知名统计域名,uBlock、AdGuard 之类默认就会拦;而 t.peroe.cn 是我自己的域名,任何拦截列表里都没有。

验证方式很直接:用 Playwright 打开首页,把 *.umami.is 的请求全部 abort(模拟装了拦截器),然后看自建计数的表现:

await page.route("**://*.umami.is/**", (route) => route.abort());   // 模拟拦截

const before = 179;   // 实验前 D1 里 "/" 的 views
await page.goto("https://blog.peroe.cn/");
await page.waitForTimeout(9000);
// 实验后:D1 的 "/" 变成 180

结果:被拦下的 Umami 请求 2 个、自建计数 +1、Umami +0。

也就是说,装了拦截器的访客在两套统计里只留下一条记录 —— 他们的每一次访问,Umami 都看不见,而自建计数看得见。中文互联网上装拦截器的比例不低,这足以解释绝大部分差距。

另外三种会放大差距的机制#

除了拦截器,还有三个我在实现里能确认的因素:

1. 脚本加载时机不同。 我把 GA4 与 Clarity 延后到 requestIdleCallback 才加载(见首屏优化那篇),Umami 的 script.js 也在那次改造后跟着延后;而 tracker.js 是页面一解析就执行。秒退的访客在 Umami 这边可能根本没等到 idle 就离开了。

2. bot 过滤不同。 Umami 会按 User-Agent 过滤已知爬虫;我的 tracker.js 只有一行 fetch,不做任何判断。这个站被扫描器盯得挺紧(Cloudflare 日志里每天都有几十个伪装 Googlebot 的请求打 /.env),它们大多不执行 JS,但只要有能跑 JS 的抓取器,自建计数就会虚高。

3. 我自己的自动化访问。 写这几篇文章的过程中,我在这台机器上把首页加载了上百次(截图、测性能、验证结构化数据)。每一次都是真实的一次 /send,也都有机会被 Umami 记上 —— 但它俩对”这次访问”的容忍度不同:我经常在页面刚 load 完就关掉浏览器,Umami 的 idle 回调还没跑。

那到底该信哪个#

我的结论是:两个都不”错”,它们量的是不同的集合。

  • Umami 更接近”人”:过滤爬虫、要求脚本加载完成,数字偏低但更像真实读者;
  • 自建 D1 更接近”请求”:只要页面跑了 JS 就记,数字偏高但能覆盖被拦截器挡掉的那部分。

所以我把它们当区间用:真实访问量大概在「Umami 的 267」到「D1 的 294」之间,偏向 Umami 那一侧(因为拦截器用户毕竟还是少数,而我的自动化访问占了不少)。

展示哪个数字#

站点侧栏显示的是 Umami 的数字:

侧栏显示的访问量取自 Umami,而不是自建 D1

理由有两个:一是它跨域名迁移后历史连续(从 2025 年记到现在),读者看到的是一条平滑上升的曲线,而不是某天突然归零;二是它过滤了爬虫,数字更”干净”。

自建 D1 则退居兜底 —— 当某个路径 Umami 还没有数据(比如刚发布几分钟的新文章)时,才用它补一下。

如果你也想接两套统计#

三条建议:

  1. 先对齐口径再比:截断时间窗、只比同一个指标,否则会得出”某套统计坏了”的错误结论;
  2. 把差异当成信息:两套的差就是”被拦截/被过滤的比例”,这本身比单一数字更有意思;
  3. 展示时选一个口径并写清楚:读者不需要知道你有两套统计,但你自己要知道屏幕上那个数字代表什么。

顺带一个实现细节:D1 的 HTTP 查询有几秒的读延迟,我刚发完一条就查会看不到。第一次实验我因此以为”自建计数没记上”,多等几秒才发现早就 +1 了 —— 排查计数问题时,先怀疑读延迟,再怀疑写入。

这些数字最终怎么显示到页面上,见浏览量统计那篇;完整的第三方清单见审计那篇。

这篇文章是否对你有帮助?

发现错误或想要改进这篇文章?

在 GitHub 上编辑此页

文章修订历史 (1 次)

查看变更记录
10月24日 23:18:07 c20be15

docs(baseline): 第 30 轮终检——数字更新到最终状态,补第 28~30 轮小结

同一个站的两套统计,为什么差了 2.7 倍
作者
peroe
发布于
10月24日 19:20
许可协议
CC BY-NC-SA 4.0

相关文章