1. 为什么需要分布式爬虫架构
当爬虫需要处理千万级甚至亿级页面时,单机爬虫会遇到几个致命瓶颈。首先是网络I/O限制,普通服务器通常只有1Gbps网卡,假设每个页面100KB,理论极限也只能每秒抓取约100个页面。其次是计算资源瓶颈,解析复杂页面时CPU可能成为瓶颈。最后是可靠性问题,单点故障会导致整个爬虫中断。
我在某电商价格监控项目中就吃过亏。最初用单机Scrapy爬取10万家店铺,运行3天后因为内存泄漏崩溃,丢失了2天的数据。后来改用分布式架构后,不仅吞吐量提升20倍,还能容忍单个节点故障。
2. 核心架构设计
2.1 组件分工
典型的分布式爬虫包含这些角色:
- 调度节点:运行Scrapy核心调度器,负责任务分配和去重
- 工作节点:运行Scrapy爬虫实例,执行实际抓取和解析
- Redis服务:作为消息队列和去重存储
- 存储集群:MySQL/MongoDB等持久化存储
2.2 数据流向
- 起始URL进入Redis的
spider:start_urls队列 - 调度节点从Redis获取URL分配给工作节点
- 工作节点抓取页面后:
- 解析出的新URL进入
spider:requests队列 - 提取的数据进入
spider:items管道
- 解析出的新URL进入
- 调度节点监控各队列状态进行负载均衡
3. Redis深度集成方案
3.1 队列设计
我们使用三种Redis数据结构:
# 待抓取队列 (List) lpush spider:requests <request_object> # 去重集合 (Set) sadd spider:dupefilter <request_fingerprint> # 抓取状态 (Hash) hset spider:stats <node_id> <current_metrics>3.2 去重优化
原生Scrapy去重占用内存大,我们改进为Redis布隆过滤器:
from pybloom_live import ScalableBloomFilter import redis class RedisBloomFilter: def __init__(self, redis_conn, key="bloomfilter"): self.redis = redis_conn self.key = key def exists(self, value): # 使用Redis的bitfield操作 pass实测显示:处理1亿条URL时,内存占用从12GB降到800MB,误判率控制在0.1%以内。
4. 高并发调优实战
4.1 网络层优化
调整Twisted反应堆参数:
# settings.py REACTOR_THREADPOOL_MAXSIZE = 50 DOWNLOADER_CLIENT_TCP_NODELAY = True DOWNLOAD_TIMEOUT = 30配合Linux内核调优:
# 增加本地端口范围 echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range # 调大TCP缓冲区 sysctl -w net.core.rmem_max=16777216 sysctl -w net.core.wmem_max=167772164.2 工作节点配置
Docker容器化部署时,每个容器限制:
- 4个Scrapy实例
- 最大并发40
- 内存上限2GB
使用Kubernetes的HorizontalPodAutoscaler根据队列长度自动扩缩容。
5. 监控与容灾方案
5.1 实时监控看板
通过Grafana展示关键指标:
- 队列积压量
- 各节点成功率/失败率
- 响应时间百分位
- 异常请求类型统计
5.2 故障处理策略
我们建立了三级容错机制:
- 重试策略:对5xx错误采用指数退避重试
- 节点隔离:连续失败超过阈值自动下线
- 数据回填:通过消息队列的死信队列处理
6. 性能对比测试
在某新闻网站爬取测试中(100万页面):
| 架构类型 | QPS | 失败率 | 资源消耗 |
|---|---|---|---|
| 单机Scrapy | 32 | 1.2% | 8核16GB |
| 基础分布式 | 215 | 0.8% | 3×4核8GB |
| 优化后架构 | 584 | 0.3% | 5×4核8GB |
7. 典型问题排查实录
7.1 Redis连接池耗尽
现象:工作节点频繁报ConnectionError排查过程:
- 检查
redis-cli info clients发现连接数爆满 - 发现Scrapy-Redis每次请求新建连接
- 修改为共享连接池:
# settings.py REDIS_PARAMS = { 'socket_timeout': 30, 'socket_connect_timeout': 30, 'retry_on_timeout': True, 'connection_pool': ConnectionPool(max_connections=200) }7.2 分布式锁竞争
在URL去重时出现性能骤降,通过Redis慢查询日志发现大量SETNX操作。最终采用分片布隆过滤器,将去重压力分散到多个Redis实例。
8. 进阶优化方向
对于特别大规模的爬取(10亿+页面),可以考虑:
- 地域分布式部署:在不同机房部署采集节点
- 动态渲染分离:将Selenium/Puppeteer节点独立部署
- 智能限速算法:根据网站响应动态调整并发
- 异构存储:热数据用Redis,冷数据转存到TiKV
这套架构在某跨国电商价格监控系统中,实现了日均3000万页面的稳定采集,平均延迟控制在2秒以内。最关键的是,在618大促期间网站改版时,我们通过动态调整抓取策略,保持了95%以上的采集成功率。