爬虫工程师都遇到过这样的场景:当目标网站数据量达到百万级时,单机爬虫的运行时间会拉长到难以接受的程度。我曾经负责过一个电商价格监控项目,单机爬取全网200万商品数据需要近40小时,而业务方要求每天更新两次。这种时间压力直接催生了对分布式爬虫的需求。
分布式爬虫的核心价值在于:
以Scrapy框架为基础构建分布式系统,既能利用其成熟的爬虫开发范式,又能突破单机性能瓶颈。下面这个对比表展示了分布式方案的优势:
| 指标 | 单机Scrapy | 分布式Scrapy |
|---|---|---|
| 日均抓取量 | 50万页 | 500万页+ |
| 故障影响范围 | 整个爬虫中断 | 单个节点下线 |
| 扩展成本 | 垂直升级硬件 | 增加普通PC |
分布式爬虫系统通常包含这些关键组件:
在Scrapy生态中,我推荐这样的技术组合:
注意:避免使用MySQL等关系型数据库做队列服务,其并发性能在分布式场景下会成为瓶颈。我曾在一个项目中因此导致队列阻塞,最终不得不进行架构重构。
良好的调度策略能显著提升分布式效率,常见模式包括:
python复制# scrapy_redis默认采用广度优先
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue'
适合层级明确的网站结构,能快速覆盖更多域名
python复制SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue'
适合需要快速获取单条链路数据的场景
python复制SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
结合业务规则动态调整URL优先级
在我的实践中,电商类项目通常采用优先级队列,将新品和促销商品的抓取优先级调高30%,这样能确保关键数据先被采集。
先安装必要的Python库:
bash复制pip install scrapy scrapy-redis redis pycurl
配置文件settings.py需要添加:
python复制# 启用Redis调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 允许暂停和恢复爬取
SCHEDULER_PERSIST = True
# 使用Redis去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# Redis连接配置
REDIS_HOST = '192.168.1.100'
REDIS_PORT = 6379
将普通Scrapy爬虫升级为分布式版本需要关注:
python复制from scrapy_redis.spiders import RedisSpider
class MySpider(RedisSpider):
name = 'distributed_spider'
redis_key = 'myspider:start_urls'
start_urls,而是通过Redis的LPUSH命令添加:bash复制redis-cli lpush myspider:start_urls http://example.com/page1
RedisPipeline暂存数据,再批量写入数据库:python复制ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300,
'myproject.pipelines.DatabasePipeline': 400
}
生产环境推荐使用Docker Compose部署,下面是一个三节点集群的配置示例:
yaml复制version: '3'
services:
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
spider1:
build: .
environment:
- REDIS_HOST=redis
depends_on:
- redis
spider2:
build: .
environment:
- REDIS_HOST=redis
depends_on:
- redis
spider3:
build: .
environment:
- REDIS_HOST=redis
depends_on:
- redis
volumes:
redis_data:
启动集群:
bash复制docker-compose up --scale spider=5
在settings.py中调整这些参数:
python复制# 全局并发数
CONCURRENT_REQUESTS = 100
# 单域名并发限制
CONCURRENT_REQUESTS_PER_DOMAIN = 20
# 下载延迟
DOWNLOAD_DELAY = 0.25
建议采用动态调整策略,通过中间件实现:
python复制class AdaptiveDelayMiddleware:
def process_request(self, request, spider):
current_load = get_redis_load() # 自定义Redis负载检测
if current_load > 80:
request.meta['download_delay'] = 0.5
Scrapy-Redis原生支持任务持久化。当需要暂停时:
SIGINT信号给爬虫进程也可以通过API主动控制:
python复制from scrapy_redis import connection
server = connection.from_settings(settings)
server.delete('myspider:requests') # 清空队列
当发现爬取速度不随节点增加而提升时,检查:
bash复制redis-cli info clients
# connected_clients应大于worker数量
bash复制iftop -i eth0
# 检查是否达到带宽上限
bash复制redis-cli SCARD myspider:dupefilter
# 过大的集合会拖慢查询速度
分布式环境下要特别注意:
python复制# 在pipeline中添加二次去重
class DeduplicationPipeline:
def __init__(self):
self.existing_ids = set()
def process_item(self, item, spider):
if item['id'] in self.existing_ids:
raise DropItem()
self.existing_ids.add(item['id'])
return item
python复制with redis.pipeline() as pipe:
while True:
try:
pipe.watch('item_count')
count = pipe.get('item_count')
pipe.multi()
pipe.set('item_count', int(count)+1)
pipe.execute()
break
except WatchError:
continue
推荐使用Prometheus+Grafana搭建监控看板,关键指标包括:
配置示例:
yaml复制# prometheus.yml
scrape_configs:
- job_name: 'spider'
static_configs:
- targets: ['spider1:8000', 'spider2:8000']
使用ELK栈收集各节点日志:
python复制# settings.py
LOG_ENABLED = True
LOG_FILE = '/var/log/scrapy.log'
LOG_STDOUT = False
LOG_LEVEL = 'INFO'
然后通过Filebeat将日志发送到Logstash:
yaml复制# filebeat.yml
filebeat.inputs:
- type: log
paths:
- /var/log/scrapy.log
output.logstash:
hosts: ["logstash:5044"]
在分布式爬虫项目中,最影响开发效率的往往不是技术实现,而是对业务逻辑的分布式改造。建议先在单机上验证爬虫逻辑完全正确,再逐步扩展为分布式架构。对于需要登录的网站,要特别注意session在多个节点间的同步问题,可以采用共享cookie池的方案解决。