[🛠] 制作公开博客统计页面
✨ GPT-5.5 的摘要
追查异常跳高的访问计数,阻止本地工作被计入访问量,并用 Cloudflare Worker 和 D1 添加公开聚合型 analytics 页面的记录。
访问计数器看起来不对。
一天的访问数突然超过了 80。
一开始也可以当作搜索流量处理。但感觉不对。最近几天我一直在修改博客,AI 工作过程中也多次打开本地服务器和公开 URL。
所以我开始怀疑。
这是真实访问吗?
还是我自己的工作也被计进去了?
计数器太诚实了
我先重新检查结构。
/zh-Hans/devlog/github-pages-blog/github-pages-blog-visitor-counter/ 中做的访问计数器使用 Cloudflare Worker 和 D1。
页面加载后,JavaScript 会向 Worker 发送 /track。Worker 再增加 D1 里的 counters、page_counters、dedupe_views。Google Analytics 只作为 baseline,之后的增量保存在 D1。
问题是,这个结构太诚实了。
浏览器打开页面,就会计数。
本地开发服务器也指向 production Worker endpoint。Worker 的 CORS 允许 origin 里也有 localhost:4000 和 127.0.0.1:4000。
所以本地预览也可能增加真实的 production D1 计数。
dedupe 的基准也是 visitorId + path。同一个浏览器 10 分钟内重复打开同一页只算一次,但连续打开不同文章会全部计入 page view。
D1 里的值很明显。
day:2026-06-06 = 14
day:2026-06-07 = 38
day:2026-06-08 = 81
day:2026-06-09 = 5
2026-06-08 那天,旧 Daily Review 和 naver-* 文章被广泛地各打开了一次。比起真实读者,更像是工作过程中检查页面。
本地工作只显示,不计数
第一步修复很简单。
本地可以显示统计,但不能发送 /track。
localhost
127.0.0.1
::1
这些环境下,客户端 JS 会跳过 tracking。
但只相信客户端还不够。如果 JS 出错,或者有人手动发请求,问题会回来。所以 Worker 也只允许 production origin 的 /track 真正计数。
TRACK_ALLOWED_ORIGIN=https://hyuk.blog
本地 origin 的请求现在会以 origin_not_tracked 被忽略。
我还加了 production QA 用的 opt-out。
?hyuk_no_track=1
?visitor_tracking=off
?visitor_tracking=on
这不是隐藏管理功能,而是我检查公开页面时避免污染计数器的开关。
统计不一定要隐藏
一开始我想做一个只有我能看的 analytics 页面。
后来发现没必要。
只要不保存原始 IP、原始 User-Agent、完整 referrer URL、单次事件日志就可以。只收集能公开的聚合值,统计页面本身也可以公开。
所以方向变了。
不是私有日志页,而是任何人都能看的公开博客统计页。
/analytics/
我把它加到顶部导航,也在侧边栏的小访问统计下面加了链接。
D1 只保存聚合
新表很简单。
analytics_daily_dimensions
date
dimension
value
count
updated_at
当一次 page view 真正被计数时,Worker 会分类公开维度,并按天累加。
维度包括:
page
content_group
locale
country
region
continent
colo
asn_org
device
viewport
browser
os
language
client_timezone
color_scheme
connection
traffic_source
referrer_domain
hour
weekday
重点是没有保存什么。
referrer 只保存 domain,不保存完整 URL。User-Agent 不保存原文,只分类为 browser、OS、device。IP 不保存。
这不是严格的数据仓库,而是个人公开博客用的流量面板。
今天、月份、全部、指定期间
一开始只想到和现有计数器一样的 今天 / 月 / 总。
但统计页需要看具体日期和期间。
/analytics API 支持:
/analytics?range=today
/analytics?range=month
/analytics?range=total
/analytics?range=custom&start=2026-06-09&end=2026-06-12
这里的 total 是从 analytics_daily_dimensions 开始收集的 2026-06-09 之后的详细聚合,不是侧边栏里包含 GA baseline 的总浏览量。
这两个意义没有混在一起。
先保证手机上能用
统计页面在手机上很容易崩。
大表格会马上变得拥挤。所以我没有做大表,而是做成条形列表卡片。
顶部只放四个数字。
选择期间
今天
本月
全部
下面是各维度的卡片。
手机上,期间按钮折成两行,KPI 是两列,详细卡片是一列。自定义日期输入也纵向排列。
桌面上,KPI 保持一行,详细卡片展开为两列。
浏览器检查时没有横向 overflow,也没有 console error。
确认过的内容
检查如下。
node --check cloudflare/ga-stats-worker.js
node --check assets/js/custom/visitor-stats.js
node --check assets/js/custom/analytics-dashboard.js
bundle exec jekyll build
npx wrangler d1 execute hyuk-blog-view-counter --remote --file cloudflare/schema.sql
npx wrangler deploy --keep-vars
部署后,/analytics?range=today 正常返回。
新的聚合表还为空。这是正常的。详细 analytics 会从部署后的真实公开访问开始累积。
我也测试了本地 origin 的 tracking。
{"status":"ignored","reason":"origin_not_tracked"}
现在本地确认页面不会增加 production counter。
从访问计数器到公开 analytics
最初只是三个小数字。
今天、月、总。
但真正运营后,更重要的不是数字本身,而是数字是否干净。对于公开博客来说,比起隐藏私有日志,展示安全的聚合流向更合适。
这不是精确账本。
但现在至少能回答更多问题。
哪些文章被读了?
从哪些国家进来?
手机多还是桌面多?
是搜索、直接访问,还是 referral?
最重要的是,我自己的工作噪声该挡到什么程度?
这就是 /analytics/ 的用途。
留下评论