日抓百万页量级,真正会崩的地方在哪?
requests.get 或者 BeautifulSoup,而是在周围的工程支撑上。分享几个在生产环境下实打实踩过的坑,希望能给做大模型数据集采集或 AI Agent 工作流的朋友提供参考。一、 队列如果没有“边界”就是垃圾场
爬虫的流量分布是非常极端的。可能一个分类页瞬间刷出几百个产品链接,或者 sitemap 更新直接扔进来五十万个 URL。如果你用的是无限制的 Redis 队列,内存会在两天内缓慢攀升,然后毫无征兆地直接 OOM(内存溢出)。
实操建议: 必须给队列设置上限。让生产者在队列满时阻塞或丢弃,而不是强行塞入。让爬虫暂停一小时没关系,但让 Broker 崩溃意味着你要花整个周末去恢复数据。
二、 避免由于重试引发的“惊群效应”
当目标网站出现短暂的 500 错误时,如果所有失败的任务都用固定的延迟时间重试,你会发现 4 万个请求在同一秒钟再次冲击对方服务器。这不仅会导致对方再次宕机,还会直接触发对方 WAF 的封禁策略。
解决方案: 必须引入全随机抖动(Full Jitter)和针对域名的熔断机制。
import random
def backoff(attempt, base=2.0, cap=300.0):
# 引入随机抖动,将重试请求均匀分散在时间窗口内
return random.uniform(0, min(cap, base * 2 ** attempt))三、 去重必须发生在“抓取前”
同一个产品可能有六个不同的 URL(带跟踪参数、颜色变体、不同分类路径等)。如果你在抓取后才去重,意味着你为了拿到一份数据,白白支付了六次请求成本,并增加了被封 IP 的风险。
在入队时就进行 Canonicalize(规范化)处理:剔除垃圾参数,统一格式,在 URL 进入队列前先过一遍 Bloom Filter(布隆过滤器),这样能极大节省资源。
四、 警惕“静默失效”的解析漂移
网站改版很少会直接让你的代码崩溃报错,更多的是“悄悄失效”。比如价格从 DOM 节点移到了一个加密的 JSON 块里,你的程序依然运行正常(Green Run),但某个字段的填充率从 100% 掉到了 30%。
避坑指南:
- 建立字段填充率监控: 为每个字段记录提取成功率,一旦与历史基线出现偏差立即报警。
- 多策略冗余: 同时部署 JSON-LD、Microdata 和 DOM 选择器三种提取方案。当其中一种失效时,其他方案能顶上,不至于让数据量瞬间归零。
五、 I/O 系统中的 CPU 陷阱
大家习惯把爬虫当成 I/O 密集型任务,于是全部用 asyncio 异步化。但当你分析性能瓶颈时会发现,解析超大 HTML 页面其实是非常消耗 CPU 的。在极高并发下,解析过程会阻塞事件循环,导致网络请求的吞吐量反而下降。这时候需要考虑将解析逻辑移交给进程池(ProcessPoolExecutor)处理。