2026.06.11 (四)

✨ 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信号不是可选项

最初实现还有点松。

visibilityStatedocumentHidden存在时会检查,但缺失时仍可能通过。这样新客户端更严格了,但省略字段直接打/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"

这不是华丽的修改。

但一旦放公开数字,就需要这种防御。浏览量是看得见的功能,所以算错会很快破坏信任。这次不是让计数器变大,而是让它更不容易被骗。

留下评论