Scrapy-Redis分布式爬虫架构实战与优化
1. 项目背景与核心挑战
最近在做一个电商价格监控项目,需要持续抓取全网15个平台的商品数据,日均新增数据量超过300万条。传统单机爬虫在数据量突破千万级时暴露出三个致命问题:爬取效率低下(单机日均抓取量不超过50万条)、断点续传困难(一旦中断需要重新爬取)、数据去重成本高(重复抓取率超过15%)。
经过两周的技术方案对比,最终采用Scrapy-Redis分布式架构配合Redis队列的方案,实现:
- 爬虫节点横向扩展(实测8节点集群日均抓取量突破1200万条)
- 断点续爬零数据丢失(服务器异常重启后5分钟内恢复进度)
- 分布式去重(重复抓取率降至0.3%以下)
2. 技术架构设计解析
2.1 核心组件选型
Scrapy框架作为基础爬虫框架,主要考虑其成熟的中间件机制和灵活的扩展性。实测对比其他框架(如PySpider)在异常处理和数据管道方面更具优势。
Redis选择5.0版本而非最新版,因为:
- 5.0版本的Stream数据类型已稳定
- 内存占用比6.0版本低12%(实测相同数据量下)
- 社区解决方案更成熟
2.2 分布式架构设计
采用主从架构而非全分布式设计,主要出于以下考虑:
- 控制节点(Master)负责URL调度和去重
- 工作节点(Worker)专注页面下载和解析
- 通过Redis的Pub/Sub实现节点通信
# 架构示意图(伪代码) class MasterNode: def __init__(self): self.redis_conn = Redis() self.scheduler = ScrapyRedisScheduler() class WorkerNode: def __init__(self): self.spider = CustomSpider() self.redis_conn = Redis()3. 关键实现细节
3.1 断点续爬实现方案
通过Redis的RDB+AOF持久化组合方案:
- RDB定时快照(每10分钟)
- AOF实时记录(appendonly yes)
- 异常恢复时优先加载AOF
# Redis配置关键参数 save 600 1 # 10分钟内至少1次修改则触发RDB appendonly yes # 开启AOF appendfsync everysec # 每秒同步3.2 分布式去重优化
传统布隆过滤器在千万级数据下内存占用过高(约1.2GB)。改用Redis的HyperLogLog:
- 相同去重效果下内存仅需12KB
- 错误率稳定在0.81%以内
- 支持分布式计数
# 去重核心代码示例 import redis from hashlib import md5 r = redis.StrictRedis() def is_duplicate(url): url_hash = md5(url.encode()).hexdigest() return r.pfadd('urls:dedup', url_hash) == 04. 性能调优实战
4.1 网络请求优化
通过Scrapy的下载中间件实现:
- 自动重试(RETRY_TIMES = 3)
- 超时动态调整(根据响应时间百分位自动计算)
- 代理IP池轮询(每请求500次切换IP)
# 动态超时中间件 class DynamicTimeoutMiddleware: def process_request(self, request, spider): p90 = spider.crawler.stats.get_value('download_time/90p') request.meta['download_timeout'] = min(p90 * 2, 60)4.2 Redis队列优化
针对高并发场景的特殊配置:
# redis.conf关键参数 maxmemory 8gb # 限制最大内存 maxmemory-policy allkeys-lru # 内存淘汰策略 tcp-keepalive 60 # 防止连接断开5. 生产环境踩坑记录
5.1 内存泄漏问题
现象:运行24小时后Worker节点内存占用超过90% 根因:Scrapy的默认统计中间件会累积所有请求记录 解决方案:
# settings.py STATS_CLASS = 'scrapy.statscollectors.MemoryStatsCollector' STATS_DUMP = False # 关闭定时统计输出5.2 分布式锁冲突
在URL调度时出现多节点同时取任务的问题。采用Redis的SETNX实现分布式锁:
def acquire_lock(lock_name, expire=10): identifier = str(uuid.uuid4()) if redis.setnx(lock_name, identifier): redis.expire(lock_name, expire) return identifier return False6. 监控方案设计
6.1 实时监控看板
使用Grafana+Prometheus监控:
- 爬取速率(items/min)
- Redis内存使用率
- 节点存活状态
- 异常请求比例
6.2 报警规则配置
关键报警阈值设置:
- Redis内存 > 75% 持续5分钟
- 节点失联 > 3分钟
- 重复抓取率 > 1%
- 平均响应时间 > 3s
7. 扩展性与维护建议
7.1 水平扩展方案
每新增一个Worker节点需要:
- 修改scrapy.cfg中的节点ID
- 调整DOWNLOAD_DELAY(建议每节点增加0.1s)
- 监控Redis连接数(maxclients参数)
7.2 长期维护技巧
- 每周执行一次Redis的MEMORY PURGE
- 每月检查一次布隆过滤器误判率
- 使用SCAN替代KEYS命令遍历数据
- 建立爬取规则的白名单机制
这套架构经过6个月的生产环境验证,在数据量从百万级增长到亿级的过程中表现稳定。最大的收获是认识到分布式系统的核心不在于技术复杂度,而在于各组件间的协调与容错设计。后续计划引入Kafka作为消息总线,进一步解耦爬取和存储模块。