实战复盘:Scrapy-Redis + 代理池 + 验证码微服务,如何稳住日采1亿数据的分布式爬虫架构 前言很多同行在交流时都会问一个问题“单机Scrapy跑得好好的为什么一上分布式就崩”答案往往不是代码写得烂而是架构没跟上数据量级的变化。当采集目标从“几万条”变成“日增1亿”时瓶颈就不再是解析逻辑而是调度去重、IP存活率、验证码通过率以及节点间的协同效率。本文不讲基础教程只聊我们在实际生产环境中踩过的坑和最终落地的架构方案。这套Scrapy-Redis 动态代理池 独立验证码识别微服务的组合拳支撑了我们内部多个核心业务线的数据供给希望给正在做大规模采集的朋友一些参考。一、 为什么是这套技术栈在日采1亿这个量级下我们面临三个核心矛盾调度瓶颈单机内存队列无法承载亿级URL去重且无法支持多节点协同。封禁对抗固定IP必死普通代理API延迟高、失败率高缺乏自愈能力。验证码阻塞打码平台调用慢嵌在Spider里会导致整个Pipeline卡顿吞吐量断崖式下跌。针对这三个痛点我们的选型逻辑如下模块选型核心理由爬虫框架Scrapy异步IO模型成熟中间件扩展性强社区生态完善分布式调度Redis (Cluster)天然支持共享队列Set去重Lua脚本保证原子性代理管理自建代理池 ABProxy本地缓存智能评分自动剔除降低外部API依赖验证码识别FastAPI GPU推理/第三方独立微服务部署解耦爬虫主流程支持批量并发数据存储Kafka → ClickHouse削峰填谷列式存储适合海量结构化数据写入二、 整体架构设计先看图再讲细节。这是我们在生产环境运行的简化架构图数据管道反爬对抗层调度层Pop/PushPop/PushPop/Push获取可用IP获取可用IP异步识别异步识别ProduceProduceConsumeConsume监控指标监控指标监控指标Redis ClusterScrapy Node 1Scrapy Node 2Scrapy Node N代理池服务验证码识别微服务KafkaClickHouseElasticsearchPrometheus Grafana关键设计点说明Redis Cluster而非单实例日采1亿意味着去重集合dupefilter可能占用数十GB内存单Redis扛不住。我们用6节点3主3从集群按domain分片存储队列。代理池独立部署不把代理逻辑写在Spider中间件里而是作为HTTP服务暴露。这样即使换爬虫框架代理能力也能复用。验证码识别异步化通过scrapy-asyncio或自定义Twisted Deferred将验证码图片下载与识别请求并行处理避免阻塞Reactor。三、 核心模块深度拆解3.1 Scrapy-Redis 的正确打开方式很多人直接用scrapy-redis默认配置结果发现性能很差。以下是我们优化后的关键点1去重策略分级不要对所有URL都用Redis Set去重亿级数据下Redis内存爆炸是迟早的事。# settings.py 中的自定义去重类classSmartDupeFilter: 三级去重策略 L1: BloomFilter (内存) - 拦截99%重复URL L2: Redis Set (持久化) - 精确去重 跨节点共享 L3: DB指纹表 (兜底) - 应对Redis故障恢复 defrequest_seen(self,request):fpself.request_fingerprint(request)# L1: 布隆过滤器快速判断误判率0.01%可接受ifself.bloom_filter.contains(fp):returnTrue# L2: Redis精确校验addedself.redis.sadd(self.key,fp)ifnotadded:returnTrue# 加入布隆过滤器self.bloom_filter.add(fp)returnFalse⚠️避坑提醒pybloom_live在Python3.10有兼容问题推荐用scalable-bloom-filter或直接调用RedisBloom模块。2队列消费模式优化默认的RPOP在高并发下存在竞争浪费。我们改用BRPOPLPUSH本地缓冲# 每个Worker预取50个URL到本地deque减少Redis网络往返classBufferedRedisQueue:BATCH_SIZE50asyncdefnext_request(self):iflen(self.local_buffer)0:# 批量获取单次RTT拿回多条itemsawaitself.redis.brpoplpush_multi(self.queue_key,self.processing_key,countself.BATCH_SIZE,timeout5)self.local_buffer.extend(items)returnself.local_buffer.popleft()ifself.local_bufferelseNone实测在32节点集群下调度吞吐提升约40%Redis CPU使用率下降25%。3.2 代理池不只是“获取IP”那么简单市面上大部分代理池开源项目只做了“获取验证”缺少动态评分和场景适配。我们自建的代理池核心逻辑如下成功率80%成功率40-80%成功率40%上游代理API入库 初始验证评分引擎优质池普通池淘汰按站点标签路由提供给爬虫节点使用后反馈结果核心代码片段基于站点维度的代理评分classProxyScorer: 不同站点对IP敏感度不同必须分开计分 key格式: proxy:{ip}:{port}:{site_domain} asyncdefreport(self,proxy:str,site:str,success:bool):keyfproxy:{proxy}:{site}pipeself.redis.pipeline()ifsuccess:pipe.hincrby(key,success,1)pipe.hset(key,last_success,int(time.time()))else:pipe.hincrby(key,fail,1)pipe.expire(key,3600)# 1小时滑动窗口awaitpipe.execute()asyncdefget_best_proxy(self,site:str,top_n:int10):获取某站点下得分最高的N个代理patternfproxy:*:{site}candidates[]asyncforkeyinself.redis.scan_iter(matchpattern,count200):dataawaitself.redis.hgetall(key)s,fint(data.get(bsuccess,0)),int(data.get(bfail,0))scores/max(sf,1)*(10.1*min(s,50))# 加权公式candidates.append((key.decode(),score))candidates.sort(keylambdax:x[1],reverseTrue)return[c[0]forcincandidates[:top_n]]经验之谈不要迷信“免费代理”。日采1亿的场景下付费隧道代理自建评分池才是性价比最优解。我们每月代理成本约8000元但节省了3台服务器和大量运维精力。3.3 验证码识别微服务解耦是关键把验证码识别塞进Spider是最常见的架构错误。正确做法是独立部署为GPU微服务# captcha_service/main.py (FastAPI)fromfastapiimportFastAPI,UploadFilefromddddocrimportDdddOcr# 或自训练ONNX模型appFastAPI()ocrDdddOcr(show_adFalse)# 全局加载避免重复初始化app.post(/captcha/recognize)asyncdefrecognize(file:UploadFile):image_bytesawaitfile.read()resultocr.classification(image_bytes)return{text:result,confidence:0.95}app.post(/captcha/batch)asyncdefbatch_recognize(files:list[UploadFile]):批量接口充分利用GPU并行能力results[]forfinfiles:imgawaitf.read()results.append(ocr.classification(img))return{texts:results}在Scrapy中异步调用importaiohttpfromscrapy.httpimportHtmlResponseclassCaptchaMiddleware:CAPTCHA_SERVICE_URLhttp://captcha-svc:8000/captcha/recognizeasyncdefprocess_response(self,request,response,spider):ifself._is_captcha_page(response):img_urlself._extract_captcha_img(response)# 异步请求验证码服务不阻塞Twisted事件循环asyncwithaiohttp.ClientSession()assession:asyncwithsession.post(self.CAPTCHA_SERVICE_URL,data{image_url:img_url})asresp:resultawaitresp.json()# 注入识别结果到meta供后续回调使用request.meta[captcha_text]result[text]returnrequest# 重新发起带验证码的请求returnresponse性能对比嵌入Spider同步调用打码平台QPS≈5独立微服务批量接口QPS≈1204卡T4。提升24倍。四、 运维与监控看不见的问题最致命日采1亿的系统没有监控等于裸奔。我们重点监控以下指标指标告警阈值说明Redis队列长度100万持续5min消费速度跟不上需扩容Worker代理可用率30%触发紧急切换上游供应商验证码识别耗时P993sGPU过载或服务异常Spider空闲率60%调度瓶颈或反爬触发Kafka Lag50万下游写入瓶颈Grafana面板示例文字描述左上实时采集速率曲线条/秒右上各节点CPU/内存热力图左下代理池健康度仪表盘右下验证码识别成功率趋势五、 成本与资源规划参考给一个真实的资源配置参考日采1亿条结构化数据组件规格数量月成本云厂商Scrapy Worker8C16G32台¥12,800Redis Cluster16G×6节点1套¥4,200验证码GPU服务T4×42台¥3,600Kafka集群标准版1套¥2,000ClickHouse32C64G×31套¥6,500代理IP费用--¥8,000合计≈¥37,100/月成本优化TipsScrapy Worker用抢占式实例Spot可节省60%计算成本Redis开启AOF持久化但关闭RDB减少IO开销ClickHouse用OSS冷存储归档历史数据。六、 写在最后分布式爬虫从来不是“搭完架子就完事”的项目。真正的壁垒在于对目标站点的理解深度、对抗策略的迭代速度、以及系统自身的弹性能力。这套架构在我们团队跑了两年多经历了多次大促期间的流量翻倍考验。它不完美但足够“抗造”。如果你也在做大规模数据采集欢迎在评论区交流你的实战经验——毕竟在这个领域踩过的坑比读过的文档更有价值。参考资料Scrapy官方文档https://docs.scrapy.org/scrapy-redis源码分析https://github.com/rmax/scrapy-redisRedisBloom模块https://redis.io/docs/data-types/probabilistic/bloom-filter/ddddocr开源项目https://github.com/sml2h3/ddddocr免责声明本文仅用于技术交流与架构学习请严格遵守《网络安全法》及robots协议合法合规开展数据采集活动。