抓取日志里有时会出现同一 URL 在短时间内返回不同内容、不同长度,甚至不同状态码的情况。排除源站更新因素后,剩下的一类常见原因来自 CDN 或反向代理的缓存层:一部分请求命中了边缘缓存,另一部分请求穿透到源站,两边拿到的版本不一致,抓取结果自然就对不上。
缓存层为什么会让抓取结果不一致
缓存命中的判断依据是缓存键。多数 CDN 默认以完整 URL 作为缓存键,但也可以把 Host、查询参数、请求头(如 Accept-Encoding、User-Agent)、Cookie 纳入或排除。只要这些规则和源站的实际内容逻辑不匹配,就会出现同一个页面被拆成多个缓存条目,或者本应区分的版本被合并成一个。
- 缓存键包含查询参数:带跟踪参数的 URL 单独成条目,命中率下降,回源次数上升。
- Vary 头设置过宽:例如 Vary: User-Agent,会让每个 UA 各存一份,蜘蛛与普通浏览器看到的内容可能来自不同副本。
- Set-Cookie 触发不缓存:源站对所有响应下发 Cookie,边缘节点可能直接跳过缓存,回源压力集中到源站。
- TTL 与更新节奏错位:内容已更新但缓存仍返回旧版本,抓取到的仍是历史页。
- 节点差异:不同地区、不同运营商节点命中情况不同,同一 URL 的抓取结果不一致。
从日志和响应头入手排查
排查的第一步是把缓存状态变成可观测的信息。多数 CDN 会在响应头里写入缓存命中标记与缓存时长,例如 X-Cache、Age、CF-Cache-Status 之类的字段,源站日志里也可以记录回源来源。把这些字段和抓取记录放在一起看,命中与回源的分布会立刻清晰起来。
- 用固定 UA 和随机 UA 分别请求同一 URL,对比响应头中的缓存标记与正文长度。
- 观察 Age 头的增长情况,判断缓存是否为新鲜副本,还是长期未刷新的旧副本。
- 检查 Vary、Cache-Control、Set-Cookie 三个响应头,确认缓存策略和源站预期是否一致。
- 抽样对比边缘命中响应与直接回源响应,逐字节核对关键区块,例如价格、库存、正文开头。
- 在抓取日志中按入口聚合,找出反复出现“命中—回源”交替的 URL,这类入口通常缓存策略最不稳定。
几类高频问题与处理方向
缓存长期返回旧内容
内容更新后没有主动刷新缓存,或者刷新只覆盖了部分节点,抓取就会读到旧版本。可以按内容类型区分 TTL:列表页、首页这类更新频繁的页面设置较短的缓存时长,详情页、静态资源可以放长一些,内容发布时对相关 URL 做一次定向刷新。
回源过于集中导致源站抖动
缓存命中率低时,抓取请求会大量落到源站,源站在峰值时段可能出现响应变慢甚至 5xx。此时应优先检查缓存键是否被参数撑开、是否因为 Cookie 或 Vary 被整体跳过缓存,而不是先考虑压缩抓取速率。
不同节点表现不一致
如果只有部分地区的节点命中异常,需要确认是节点预热未完成,还是该区域的回源线路本身不稳定。可以在抓取日志中按时间段对比命中率,配合节点的刷新记录定位。
调优时的几个取舍
- 缓存键尽量稳定,避免把与内容无关的请求头纳入计算。
- 除非确实存在按 UA 分发不同内容的逻辑,否则不要让 Vary: User-Agent 影响缓存。
- 对爬虫 UA 不要单独放行到源站,这会让缓存层形同虚设。
- 内容更新后的刷新范围和 TTL 要配套,只改其中一个往往解决不了问题。
- 把缓存命中率和源站响应时间一起看,单看其中一个指标容易误判。
缓存命中率高并不等于抓取正常。判断标准应该是边缘返回的内容与源站当前内容是否一致,而不是命中比例本身。
缓存层是抓取路径上容易被忽略的一环,它既可能帮站点扛住抓取压力,也可能因为策略配置不一致,让蜘蛛看到和用户不一样的页面。把响应头的缓存信息纳入日常观察,遇到抓取内容异常时先分清“命中版本”和“回源版本”,排查方向会明确很多。