做蜘蛛池的人,注意力大多放在入口页的内容差异、互链结构和数量规划上,却容易忽略一个更底层的问题:爬虫请求那个 URL 时,返回的 HTML 到底是谁生成的。如果入口页前面挂了 CDN、反向代理或者页面缓存,爬虫拿到的很可能不是你以为的那一版。
为什么缓存会改变抓取结果
爬虫是按 URL 取页面的。中间只要存在缓存层,返回内容就可能来自缓存副本,而不是源站实时渲染的结果。对蜘蛛池来说,入口页通常是批量生成、批量调整的,模板、锚文本、内链指向可能一天改好几轮。一旦缓存 TTL 设得很长,爬虫今天、明天、一周之后看到的都是同一份快照,你在源站做的更新动作等于打折执行。
更麻烦的是,缓存本身不是错误配置,它在正常场景下就是提升响应速度的手段。问题在于入口页的更新节奏,和缓存的有效期不匹配。
三种缓存形态,影响程度不一样
- 浏览器缓存:主要影响真实用户,对爬虫的影响相对小,因为爬虫往往不带本地缓存。但如果响应头写成强缓存,某些抓取工具复用连接时也可能读到旧内容。
- 边缘节点缓存:影响最大。回源频率由节点决定,各地节点回源时间点不同,就可能出现同一个 URL 在不同节点上是不同版本。
- 服务端页面缓存或对象缓存:源站内部先缓存一份渲染结果,只要缓存键设计不合理,入口页之间的差异内容可能被串用,出现 A 页显示 B 页锚文本的情况。
真正影响爬虫的那几个响应头
- Cache-Control:重点看 max-age、s-maxage、no-cache、no-store、private。s-maxage 决定共享缓存(含 CDN)的有效期,常被忽略。
- Expires:老式写法,和 Cache-Control 同时存在时以 Cache-Control 为准,但两边冲突会让人误判。
- ETag 与 Last-Modified:用于条件请求,能让回源变轻,但对内容是否为最新没有保证。
- Vary:写成 Vary: User-Agent 时,不同 UA 会被分配到不同缓存副本,入口页内容一致性很难保证。
- Age 与命中标记:Age 大于 0 基本可以判断这次返回来自缓存,排查时比看内容更直接。
同一 URL,不同节点版本不一致
入口页批量更新后,如果没有主动刷新,常见现象是:一部分节点已经回源拿到新版本,另一部分还在提供旧版本。爬虫从不同出口 IP 请求,就有可能交替拿到新旧两份内容。表现出来就是抓取记录不连贯、锚文本对不上、页面摘要前后矛盾。
如果入口页需要保持内容一致,这类情况比单纯的内容陈旧更难排查,因为源站自己测是正常的。
入口页更新与缓存刷新的配合
- 入口页单独一套缓存策略,不要和目标页共用同一组规则。
- 批量生成或批量改模板后,主动按路径刷新,而不是等 TTL 自然过期。
- TTL 建议从短开始,比如几分钟到几小时,观察回源压力和抓取情况再决定是否放宽。
- 需要在更新后马上被读到的内容,用短 TTL 加主动刷新组合,比把 TTL 全关掉更稳。
一个常用排查流程
- 用 curl -I 直接看响应头里的 Age 和命中标记,先确认是否命中缓存。
- 换不同 UA、不同出口 IP 请求同一个 URL,对比返回内容与 Age。
- 检查是否有 Vary: User-Agent 这类容易造成多副本的写法。
- 对比源站直连返回内容与节点返回内容,定位差异发生在哪一层。
常见误区:以为改了源站内容,爬虫下次来就一定能看到。实际决定权在缓存策略和刷新动作上,源站改了但缓存没动,爬虫看到的仍然是旧版本。
几点落地建议
- 不要把缓存键里塞入随机参数,否则每个 URL 都是独立副本,既失去缓存意义,也让爬虫反复请求相同内容。
- 入口页的更新动作和缓存刷新动作,尽量在同一个流程里完成,避免只有一边执行。
- 如果入口页需要长期稳定不变,可以放宽 TTL;如果需要频繁调整,就保持短 TTL。
- 缓存命中带来的响应速度提升,对抓取节奏是正向的,但内容陈旧会削弱爬虫再访的意愿,两者要一起权衡。
缓存不是收录的开关,但它会让你的更新动作打折扣。把缓存策略和入口页的更新节奏对齐,往往是投入最小、见效最直接的一步。