[🛠] 减少博客统计中的机器人计数
✨ GPT-5.5 的摘要
为了减少Cloudflare Worker访问统计中混入的云端爬虫和轻量headless访问,加入ASN/组织过滤和visible engagement信号,并解除误拦的ISP ASN的记录。
统计数字又变得奇怪。
加上公开博客统计页面之后,至少本地工作不再污染production计数器了。但这次出现了另一个问题。
有些请求不像人在阅读。
页面一打开就快速扫过多个路径,User-Agent看起来像浏览器,但不像真实读者那样停留。公开静态博客有爬虫是正常的。问题是如果爬虫也打到/track,公开浏览量和访问数也会一起膨胀。
第一次加入访客计数器时,我写过机器人和重复访问已经适当拦截。实际运行后发现,“适当”的标准还得再提高一点。
只看User-Agent不够
原来的Worker里已经有基本的机器人判断。
bot user-agent
Cloudflare verified bot
Cloudflare bot score
dedupe window
明显的机器人大致能被拦住。
但不是所有爬虫都会在User-Agent里写bot。有些请求看起来像普通Chrome。只靠User-Agent,很难区分人和轻量自动化浏览器。
Cloudflare提供的request.cf里有ASN和ASN organization。所以我让/track也检查这些信息。
TRACK_BLOCKED_ASNS
TRACK_BLOCKED_AS_ORGS
环境变量不是完全覆盖默认值,而是在Worker内置默认值上追加。明确要拦的轴留在代码里,运行中发现的新值通过环境变量补充。
组织名只做完全一致也太弱。
Huawei Cloud
Huawei-Cloud-HK
Huawei Cloud Singapore POP
Huawei Clouds Singapore
名称可能这样变化。所以组织名比较同时允许exact match和contained string。这样一个默认值可以捕捉多个变体。
加入3秒visible engagement
只靠ASN过滤太偏网络层。
正常读者也可能在某个ISP或公司网络之后。爬虫也可能来自看起来普通的网络。所以客户端信号也必须一起看。
这次客户端只有在页面实际可见一小段时间之后才发送/track。
track_delay_seconds = 3
3秒并不长。真正阅读的人几乎不会感觉到。但它可以过滤一部分瞬间预览、后台标签页抓取、打开后马上消失的页面加载。
客户端会在/track payload里发送这些值。
engagementMs
visibilityState
documentHidden
viewportWidth
viewportHeight
Worker会再次验证。
不能因为客户端JS等过了就完全相信它。服务器端如果发现engagementMs短于阈值,就以client_visible_too_short忽略。viewport为0或无效,则以client_viewport_invalid忽略。
visible信号不是可选项
最初实现还有点松。
visibilityState和documentHidden存在时会检查,但缺失时仍可能通过。这样新客户端更严格了,但省略字段直接打/track的请求还能留下来。
所以Worker条件变得更硬。
visibilityState === "visible"
documentHidden === false
这两个条件只要不精确满足,就以client_signal_missing忽略。
关键是,这里把hidden和missing当成同一类问题处理。/track是增加公开浏览量的endpoint。进入这个endpoint的请求,应该像正常页面里的正常客户端发出的请求。没有信号,就没有放行理由。
结果是,/analytics读取仍然公开,但/track更严格了。
读取统计
-> 可公开
增加浏览量
-> production origin
-> visible engagement
-> 正常viewport
-> 通过bot/ASN/org过滤
拦截值必须持续复查
这次还学到一点:blocklist不是越宽越好。
为了抓云端爬虫,如果把正常ISP ASN也放进默认拦截值里,真实读者记录也会掉。所以我从内置拦截ASN里移除了误加的值。
过滤器越强看起来越好,但在统计里false positive也有成本。
把机器人算进去,数字会膨胀。
把人拦掉,真实读者会被抹掉。
两个都不好。
所以这次标准整理成这样。
明确的云端/爬虫轴在Worker里拦截。
客户端visible信号必须存在。
像ISP这种可能承载真人流量的轴,不放入内置拦截值。
可疑值用ignored_reason留下,方便之后审计。
analytics_events会给被忽略的请求也留下最小信息和ignored_reason。没有这个,之后就很难解释数字为什么减少、到底拦了什么。
数字要尽快显示,但不能太容易计数
访客计数器站在一个尴尬的平衡点上。
反映太慢会看起来像坏了。所以访客计数器工作里使用了GA baseline加D1即时增量的结构。
但计数太容易,机器人也会一起算进去。
这次工作把平衡稍微往谨慎一侧移动。正常读者仍然会在3秒后被计数。但瞬间掠过的自动化、hidden document、无效viewport、已知云端爬虫轴,更难进入计数器。
公开统计不是精确账本。
但至少应该接近“有人读过”的痕迹。数字大不是目的。数字可信,才方便做下一步判断。
确认过的内容
工作中确认了这些。
node --check cloudflare/ga-stats-worker.js
node --check assets/js/custom/visitor-stats.js
git diff --check
bundle exec jekyll build
Cloudflare Worker deploy
GitHub Pages deploy
也做了Worker smoke test。
/analytics?range=today
-> trackDelaySeconds: 3
/track without visibilityState
-> ignored, client_signal_missing
/track with visibilityState="hidden"
-> ignored, client_signal_missing
/track with documentHidden=true
-> ignored, client_signal_missing
也确认了部署后的首页HTML里有data-track-delay-seconds="3"。
这不是华丽的修改。
但一旦放公开数字,就需要这种防御。浏览量是看得见的功能,所以算错会很快破坏信任。这次不是让计数器变大,而是让它更不容易被骗。
留下评论