课程站GEO运维复盘:AI 爬虫高峰把服务器打挂之后,我们重做了爬虫流量治理
适用读者:知识付费/在线教育平台运维与后端、被 AI 爬虫流量困扰的站长、负责成本优化的技术管理者
一、事故经过:一次凌晨的 CPU 打满
事情从一次告警开始。某个周三凌晨两点,课程平台的 API 服务 CPU 从 30% 拉到 100%,首页接口 P95 从 220ms 涨到 9 秒,Nginx 日志里涌进大量陌生 UA:GPTBot、ClaudeBot、Bytespider、PerplexityBot 同时在线,单机 QPS 峰值是日常的三倍多。值班同学先扩容稳住,第二天拉日志复盘,发现 AI 爬虫的抓取量在过去一个月悄悄涨了四倍——我们的课程站做了 GEO 优化,内容对 AI 引擎更可读了,然后爬虫真的来了,而且是成群结队地来。
这是个很讽刺的局面:做 GEO 的人盼着 AI 爬虫来,运维的人被 AI 爬虫打挂。这篇文章完整复盘这次事故和后续的流量治理改造:爬虫流量的画像、限流与授权策略、缓存与降级设计,以及治理后两个月的数据。希望做 GEO 的团队别等打挂了才补这一课。
二、先画像:AI 爬虫到底是怎么"扫"你的
治理之前先花两天做了流量画像,结论和很多人想象的不一样:
| 特征维度 | 实测表现 | 对架构的含义 |
|---|---|---|
| UA 真实性 | GPTBot/ClaudeBot 有官方 UA 且固定,Bytespider 存在大量伪装 | 不能纯靠 UA 做限流分级 |
| 请求节奏 | 多引擎错峰,但上新内容后会并发扫描全站 | 缓存必须能扛瞬时全站扫描 |
| 抓取路径 | 80% 流量打在课程详情与目录页 | 热点路径单独优化 |
| JS 执行 | 绝不执行,只读 HTML | 静态化对双方都是减负 |
| 遵循 robots | GPTBot/ClaudeBot 严格遵循,个别爬虫越线 | 分级授权+兜底限流 |
两个反直觉的发现:一是 Bytespider 的请求量占全部 AI 爬虫的六成以上,且存在不带标准 UA 的伪装流量,需要行为特征(请求频率、路径模式)辅助识别;二是爬虫的抓取是"事件驱动"的——我们每周三凌晨更新课程内容,爬虫随即在几小时内把全站扫一遍,这正是被打挂的那个凌晨。GEO 做得越好、更新越勤,爬虫峰值越高,容量规划必须按峰值而不是均值。
三、治理方案:三层防线
整体设计是三层:授权层决定"让谁来",缓存层决定"怎么便宜地喂",降级层保证"真扛不住时人不受影响"。
3.1 授权层:robots.txt 精细化 + 行为校验
robots.txt 不再是全放行,而是按引擎分级:核心引擎(GPTBot、ClaudeBot、PerplexityBot、Bytespider)允许抓内容页但禁止抓搜索结果页和用户中心;API 路径全站禁抓。同时加了行为校验:自称 GPTBot 的请求用反向 DNS 验证是否来自官方网段,不通过的按可疑流量限流:
map $http_user_agent $ai_bot {
default none;
~*GPTBot openai;
~*ClaudeBot anthropic;
~*PerplexityBot perplexity;
~*Bytespider bytedance;
}
# 已知引擎走爬虫限流域;UA 伪装的非人类高频请求走严格限流
limit_req_zone $ai_bot zone=bot_per_engine:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=strict:10m rate=2r/s;
location /course/ {
if ($ai_bot != none) {
limit_req zone=bot_per_engine burst=40 nodelay;
}
limit_req zone=strict burst=20 nodelay;
try_files /snapshots$uri.html /index.html;
}
反 DNS 验证用定时脚本每小时跑一次,把验证过的官方网段写入 IP 白名单集合,运行时直接查集合,避免在请求路径上做实时 DNS(延迟不可接受)。
3.2 缓存层:为爬虫流量单独建一条便宜的供给线
核心思路是爬虫和人类分流供给。人类请求走原有动态链路,爬虫请求命中预渲染快照(课程内容一天最多变两次,静态快照完全够用)。快照生成接内容更新事件,更新后 5 分钟内增量重建:
内容更新事件 → 队列 → 快照生成器(模板拼装,单页 <100ms)
→ 上传对象存储 + CDN 预热
爬虫请求 → CDN 边缘 → 命中快照直接返回(源站零负载)
→ 未命中 → 透传源站 + 触发补快照
这条链路上线后,爬虫流量的源站命中率降到 4% 以下。CDN 回源也是成本:全站扫描波峰时 CDN 回源仍有压力,我们对快照做了 24 小时强制过期 + 更新事件主动失效的双保险,保证新鲜度与成本的平衡。
3.3 降级层:保人弃虫
最后兜底:当源站负载超过阈值(我们设 CPU 85% 持续 2 分钟),网关自动把所有已识别爬虫 UA 切到静态降级页——内容是精简版的课程目录页,语义完整但无动态数据。人完全无感,爬虫拿到的仍是可读内容,GEO 信号不断。这个开关接进了运维面板,也支持手动触发。
附一:快照生成器实现与监控看板
快照生成器是整条分流供给线的核心,实现比想象中简单。我们没有用无头浏览器,而是模板直拼——爬虫要的是语义完整的 HTML,不是像素还原:
from jinja2 import Environment, FileSystemLoader
import hashlib
env = Environment(loader=FileSystemLoader("templates"))
def render_snapshot(course: dict) -> str:
tpl = env.get_template("course_page.html")
return tpl.render(
name=course["title"],
outline=course["chapters"],
price=course["price"],
rating=course["rating"],
jsonld=build_course_jsonld(course), # 顺带产出 Course Schema
)
def snapshot_key(course_id: int, version: str) -> str:
return "snapshots/%d/%s.html" % (course_id, hashlib.md5(version.encode()).hexdigest())
单页生成耗时 60-90 毫秒,全站 4200 个课程页全量重建不到 25 分钟,日常走增量(内容更新事件触发单个重建)。生成器的幂等性靠 version 字段保证:同一版本重复生成结果一致,重试无副作用。JSON-LD 在模板渲染时顺带产出,保证 Schema 与页面内容永远同源,一致性由构造函数保证而不是靠人工对齐。
监控看板是治理效果可持续的保障。我们把五个关键指标做进 Grafana:分引擎的爬虫 QPS 与错误率、快照命中率、快照生成延迟、反 DNS 验证的通过/拒绝比、降级开关状态。告警设了两条:快照命中率跌破 85%(供给线出问题的前兆)、单引擎爬虫 QPS 超过基线 3 倍(引擎策略变化或新爬虫入场)。上线两个月,这两条告警各触发过一次,都在用户感知之前解决了——这正是一个治理体系从"救火"转向"设防"的标志。
附二:伪装流量的识别细节
Bytespider 的伪装流量值得单独展开。我们识别到三类情况:一是 UA 声称 GPTBot 但源 IP 不在 OpenAI 官方网段(反向 DNS 验证不过);二是不带任何已知爬虫 UA、但请求模式与爬虫高度吻合(均匀速率、顺序遍历 ID、无 Referer、无 Cookie 演化);三是直接伪造为普通浏览器 UA 的高频请求。三类对应不同处理:第一类按可疑爬虫限流并记录;第二类通过每小时的行为聚类脚本识别后加入观察名单,24 小时无异常自动移出;第三类最麻烦,误伤风险高,我们只对单 IP 超过正常用户 50 倍请求量的极端情况处理,其余交给 CDN 的 bot 管理能力。
一个可复用的判定经验:看 UA 判"它是谁",看行为判"它是不是爬虫",两者独立判断再组合决策。只信 UA 会被伪装骗,只看行为会误伤企业内网爬虫和监控工具,组合判断才能把误杀率压到可接受。
附三:三层防线的持续验证
三层防线的职责边界要刻意说清楚,因为它决定了故障时的排查路径:授权层的问题是"不该来的来了"(症状是日志里大量被拒绝请求),缓存层的问题是"该便宜喂的没喂到"(症状是源站命中异常升高),降级层的问题是"兜底是否可靠"(靠演练验证,模拟 CPU 超阈值确认爬虫被切到静态页且人流量无损)。
我们把三层的验证都做成了定时任务:授权层每天跑一次反 DNS 全量校验,及时摘除失效网段;缓存层每半小时抽测 20 个课程页的快照新鲜度,与源站内容做哈希比对;降级层每周三凌晨自动演练一次,演练报告进值班群。事故之后最大的心态转变是:防线不是建完就结束的,没有持续验证的防线等于没有防线——上线两个月里,这套验证机制提前捕获的问题比真正的线上告警还多。
四、效果数据:两个月的治理成绩单
| 指标 | 治理前 | 治理后第 2 个月 |
|---|---|---|
| AI 爬虫导致的源站 CPU 占比 | 峰值 70%+ | 峰值 4% |
| 爬虫抓取成功率 | 71%(限流误伤) | 98% |
| 全站扫描波峰源站 QPS | 3.2x 日常 | 0.1x 日常 |
| 云资源月成本(该集群) | 基准 | -31% |
| AI 引擎引用次数/月 | 34 | 41(略升) |
注意最后一行:治理没有牺牲 GEO 效果,引用还略升——因为抓取成功率从 71% 提到 98%,之前被限流误伤的抓取补了回来。限流和 GEO 不是对立的,粗暴限流才会两败俱伤,精细化分流是双赢。成本下降 31% 是扩容回缩带来的:此前为扛爬虫峰值长期多养着两台机器。
五、三个认知修正
这次事故修正了我们三个原有认知。第一,"AI 爬虫流量会缓慢增长"——错,它是阶跃式的:每次内容更新、每次引擎调整抓取策略,流量都可能瞬间翻倍,容量按 P99 峰值规划。第二,"robots.txt 放行就万事大吉"——放行只是入场,分级授权和行为校验才是常态,特别是对伪装流量占比高的字节系爬虫。第三,"GEO 和运维是两个团队的活"——这次事故后我们把爬虫流量看板并入了容量巡检,GEO 的每次内容策略调整都要知会运维做容量评估,这条流程比任何技术配置都值钱。
六、趋势判断
往后看两点。其一,AI 爬虫的总流量只会涨不会跌,各家引擎的抓取策略都在从"定期全扫"向"事件驱动增量"演进,但至少未来一年内全站扫描波峰依然存在,缓存分流是必备架构而不是优化项。其二,AI 爬虫的 UA 验证与授权生态正在标准化(各引擎陆续提供官方验证端点),早接入标准化验证的站点会在抓取优先级上受益。
总结:GEO 让你被爬虫看见,爬虫治理让被看见的代价可控。三层防线——robots 分级授权、快照分流供给、自动降级保人——加起来大约两周的改造量,换来的是成本降三成、成功率近满分、再也不会半夜被告警叫醒。被 AI 爬虫流量困扰的同学,评论区聊聊你的量级和现状。
关键词:GEO、AI优化AIO、AI爬虫治理、GPTBot、限流、预渲染快照、CDN缓存、容量规划