直接回答:增量的核心是三件事——判断哪些内容变了、只重算变化影响到的部分、单独处理内容消失的情况。做到这三点,采集任务才可能长期稳定运行;继续全量重跑,时间、调用成本和目标站点的压力都会迅速失控。
全量重跑为什么走不远
原型阶段全量重跑没问题:站点少、内容少、跑一次几分钟。但三个成本会随规模放大:
| 成本 | 表现形式 |
|---|---|
| 时间 | 线性增长,最终超过周期本身 |
| 计算与调用 | 向量化与模型调用的费用成倍上升 |
| 外部压力 | 对目标站点反复请求,容易被限速甚至封禁 |
第二项最容易被低估。向量化是按量计费的,如果每周把几千篇文章重新算一遍向量,而其中九成内容没有任何变化,这笔开销纯属浪费。
第三项是关系问题:对别人的站点保持礼貌,是长期运行的前提。这一点与《robots.txt 里的 AI 爬虫该怎么配》里的原则一致——频率控制既是技术问题,也是态度问题。
变化检测有三种信号,按成本从低到高用
信号一:HTTP 条件请求(最省)
带上上次请求返回的校验信息再要一次:
1 | GET /posts/hello/ |
服务端如果判断没变化,会返回 304 Not Modified,只有响应头、没有正文。这是成本最低的一层过滤。
两个注意点:不是所有站点都正确支持;304 只说明服务端认为没变,不代表内容真的没变(有些站点永远返回 200)。
信号二:正文哈希(最可靠)
把抓到的正文算一个哈希值,与上次比较,不同就说明内容变了。
关键是先剥离模板部分再哈希:导航、页脚、侧边栏里的"最新文章"列表每天都会变,如果把它们算进去,每次哈希都不一样,增量就失效了。
1 | 抓取 → 正文提取(同采集阶段)→ 归一化(去空白、去时间戳)→ 哈希 → 比对 |
信号三:站点地图与订阅源(最便宜地发现"新增")
sitemap.xml 里的 lastmod、RSS 里的 pubDate,都能直接告诉你哪些地址是新出现的。这一层适合用来发现新增内容,但用于判断"内容是否被修改"并不可靠——很多站点不更新 lastmod。
实用的组合是:用 sitemap/RSS 发现新增,用条件请求降低请求量,用正文哈希做最终判定。
重算范围要按变化类型区分
发现内容变了之后,不是所有处理都要重做。区分清楚能省下大量算力:
| 变化类型 | 需要重做的事 |
|---|---|
| 正文文本变化 | 重新切分片段、重新向量化 |
| 标题或小标题变化 | 重新切分,正文片段向量可保留 |
| 仅发布时间/作者等元数据变化 | 只更新字段,不动向量 |
| 页面结构变化(模板调整) | 重新提取正文并比对,可能不需要重算向量 |
只有正文文本真正变化时才需要重新向量化,这是最花钱的一步。把判断条件写严格一点,长期省下的开销很可观。
内容消失和地址变更必须单独处理
这是最容易被忽略的一类情况。页面上一次抓取还在,这次返回 404 或者跳到了别的地址。
处理原则:
- 返回 404 时不要立刻删除,先标记为"疑似失效",连续几次确认后再置为失效状态
- 返回 301/302 时记录新地址,把新旧地址关联起来,避免同一内容被当成两份
- 保留历史版本:内容变了不代表旧版本没价值,检索到旧片段时至少要知道它已经过期
**为什么不能直接删?**因为目标站点可能只是临时故障,或者做了短时间的维护。直接删除会让检索结果出现空洞,而恢复成本远高于保留状态标记。
任务调度要限速,也要能中断续跑
增量任务的一个现实约束是:它会被中断。
可能的原因很多——网络波动、目标站点超时、服务器重启。所以调度设计要满足两点:
**第一,任务粒度足够小。**以"单个页面"为最小单位,而不是"整站"。中断后只需重跑未完成的部分。
**第二,状态要持久化。**每个页面的处理状态(待处理、已抓取、已提取、已向量化)写进数据库,而不是留在内存里。
1 | pending → fetched → extracted → embedded → done |
配合一个简单的并发上限和请求间隔,任务就能在无人值守的情况下长期运行。
一个最小可行的增量方案
把上面几点合起来,一个够用的方案是:
- 从
sitemap.xml和 RSS 拉取地址列表,与库里比对,找出新增地址 - 对已有地址发条件请求,拿到
200的加入待检查队列(拿到304的直接跳过) - 对待检查的页面提取正文、归一化、算哈希
- 哈希不同的,按变化类型决定是否重新切分与向量化
- 对未出现在新列表里的地址,标记为疑似失效,多次确认后再置失效
- 全程记录状态,支持中断续跑
这套流程不需要复杂调度框架,一张状态表加一个循环就能跑起来。先让它稳定运行,再考虑优化并发和分布式。
常见问题(FAQ)
增量带来的复杂度值得吗?
取决于规模和周期。内容在几百篇以内、每周跑一次,全量重跑完全够用。**一旦出现"跑不完一个周期"或者向量化开销明显上升,就该做增量了。**过早引入增量会让原型阶段的迭代变慢。
条件请求返回 304,还需要比对哈希吗?
通常不需要,304 已经说明服务端认为内容未变。两种情况例外:站点返回的 ETag 生成规则不稳定(例如每次都变),或者站点从不返回有效校验头。遇到这类站点就直接进入哈希比对,不要依赖条件请求。
内容变了但只想更新向量,不想重新切分可以吗?
不建议。正文变化后,片段边界通常也会变化——只在旧片段上重算向量,会让片段与内容不再对应,检索结果会出现错位。切分和向量化应该作为一组操作处理。
抓取频率怎么定比较合适?
三个约束取最小值:robots.txt 里的 Crawl-delay(如果有)、目标站点的响应时间、以及你自己的周期要求。**实践上从保守值开始,观察目标站点是否返回 429 或 503,再逐步调整。**宁可跑慢一点,也不要因为请求过密被拉黑。
小结
- 全量重跑的时间、算力与外部压力都会随规模放大,增量是稳定运行的前提。
- 三种变化信号按成本使用:条件请求、正文哈希、sitemap/RSS。
- 哈希前必须剥离模板部分,否则每次都会被判定为"已变化"。
- 只有正文真正变化时才重新向量化,这是最省钱的优化。
- 内容消失要标记而非删除,任务状态要持久化以支持中断续跑。
上一篇是本系列的《内容采集与结构化流水线》,架构总览见《整体架构与技术选型》。如果你的增量方案有更省事的做法,欢迎到留言板交流。