1. 项目概述:Scrapy-Redis分布式爬虫实战
电商数据抓取是当前大数据领域的热门需求,但单机爬虫在面对千万级数据时往往力不从心。Scrapy-Redis作为Scrapy框架的分布式扩展,通过Redis实现任务调度和数据共享,能够轻松突破单机性能瓶颈。我在最近一个电商价格监控项目中,使用5个节点组成的集群,成功实现了日均300万条商品数据的稳定抓取。
这个方案的核心价值在于:
- 通过Redis的队列机制实现请求去重和任务分发
- 支持动态扩展爬虫节点数量应对不同规模的目标网站
- 内置断点续爬功能,避免因异常中断导致数据丢失
- 天然支持分布式存储,便于后续数据处理流水线集成
2. 技术选型与架构设计
2.1 为什么选择Scrapy-Redis
传统Scrapy爬虫在分布式场景下存在三个致命缺陷:
- 调度器单点问题 - 所有URL都存储在内存队列中
- 无法共享去重指纹 - 各节点独立维护已爬取集合
- 任务分配不均 - 缺乏中心化的任务分发机制
Scrapy-Redis通过以下设计解决了这些问题:
- 将待爬队列迁移到Redis,实现全局可见
- 使用Redis的Set结构存储指纹集合
- 采用先进先出队列保证公平调度
2.2 Redis的关键作用
在我们的架构中,Redis承担了三大核心功能:
| 功能模块 | 数据结构 | 性能指标 | 容量规划 |
|---|---|---|---|
| 请求队列 | List | 10万QPS | 按URL平均长度1KB计 |
| 去重集合 | Set | 5万次/秒查询 | 预留20GB内存 |
| 数据暂存 | Hash | 1万次/秒写入 | 按数据保留3天计 |
实测表明,单个Redis实例在16核32G配置下,完全可以支撑10个爬虫节点的并发工作。
3. 环境搭建与配置详解
3.1 基础环境准备
以下是我们的节点配置方案:
bash复制# 所有节点通用安装
pip install scrapy scrapy-redis redis==3.5.3
# 建议使用虚拟环境
python -m venv scrapy_env
source scrapy_env/bin/activate
3.2 Scrapy-Redis关键配置
在settings.py中必须包含以下配置:
python复制# 启用Redis调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 启用Redis去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 保持爬虫持续运行
SCHEDULER_PERSIST = True
# Redis连接配置
REDIS_HOST = '192.168.1.100' # Redis服务器IP
REDIS_PORT = 6379
REDIS_PASSWORD = 'yourpassword' # 生产环境务必设置
# 并发设置
CONCURRENT_REQUESTS = 32 # 每个节点的并发数
重要提示:REDIS_PASSWORD一定要设置,我们曾因密码泄露导致爬虫被恶意注入非法URL
4. 爬虫开发实战
4.1 继承RedisSpider
电商爬虫的基本结构示例:
python复制from scrapy_redis.spiders import RedisSpider
class EcommerceSpider(RedisSpider):
name = 'jd_product'
redis_key = 'jd:start_urls' # Redis中的起始URL键
def parse(self, response):
# 提取商品数据
item = {
'sku': response.css('#sku::attr(value)').get(),
'price': response.css('.price::text').get(),
'title': response.css('title::text').get()
}
# 自动将新URL加入队列
for url in response.css('.related-items::attr(href)').getall():
yield scrapy.Request(url, callback=self.parse)
yield item
4.2 分布式控制策略
我们采用了三级URL优先级策略:
- 商品详情页(最高优先级)
- 分类列表页(中等优先级)
- 搜索页(低优先级)
通过Redis的LPUSH/RPUSH实现优先级控制:
python复制# 在管理节点执行
import redis
r = redis.StrictRedis(host='192.168.1.100')
# 高优先级URL插入队列头部
r.lpush('jd:start_urls', 'https://item.jd.com/100123456.html')
# 低优先级URL插入队列尾部
r.rpush('jd:start_urls', 'https://search.jd.com/list.html')
5. 性能优化技巧
5.1 去重优化
默认的RFPDupeFilter会存储完整URL的指纹,我们通过重写指纹算法节省了40%内存:
python复制from scrapy.utils.request import request_fingerprint
def custom_fingerprint(request):
# 只对商品ID部分计算指纹
url = request.url
product_id = re.search(r'/(\d+)\.html', url).group(1)
return hashlib.sha1(product_id.encode()).hexdigest()
DUPEFILTER_CLASS = 'project.custom_filters.CustomDupeFilter'
5.2 异常处理机制
电商网站常见的反爬策略应对方案:
- 频率限制 - 使用DOWNLOAD_DELAY和自动限速扩展
- 验证码 - 集成第三方打码平台
- IP封禁 - 搭建代理IP池
我们的重试中间件配置:
python复制RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 400, 403, 408]
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 90,
'scrapy_proxies.RandomProxy': 100,
}
6. 数据存储方案
6.1 分布式存储架构
我们采用Redis+MongoDB的混合存储方案:
code复制爬虫节点 → Redis临时存储 → Kafka → MongoDB集群 → 数据分析平台
这种设计的好处:
- Redis保证写入速度
- Kafka消峰填谷
- MongoDB适合文档型数据存储
6.2 MongoDB优化配置
python复制# pipelines.py
import pymongo
class MongoPipeline(object):
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
# 创建索引
self.db.products.create_index([('sku', pymongo.ASCENDING)], unique=True)
def process_item(self, item, spider):
self.db.products.update_one(
{'sku': item['sku']},
{'$set': dict(item)},
upsert=True
)
return item
7. 运维监控体系
7.1 监控指标设计
我们使用Prometheus+Grafana搭建的监控系统跟踪以下指标:
- 请求成功率
- 平均下载延迟
- Redis内存使用率
- 各节点任务吞吐量
- 异常响应统计
7.2 告警规则示例
yaml复制groups:
- name: spider-alerts
rules:
- alert: HighErrorRate
expr: sum(rate(scrapy_httpstatus_count{status=~"4..|5.."}[5m])) by(spider) / sum(rate(scrapy_httpstatus_count[5m])) by(spider) > 0.1
for: 10m
labels:
severity: critical
annotations:
summary: "High error rate on {{ $labels.spider }}"
description: "Error rate is {{ $value }}"
8. 实战经验总结
在完成这个千万级电商爬虫项目后,我总结了以下几点关键经验:
- 容量规划要提前 - Redis内存至少预留30%buffer
- 监控要全方位 - 不仅监控爬虫,还要监控Redis和存储系统
- 去重策略要优化 - 根据业务特点定制指纹算法
- 反爬应对要分层 - 不同级别的反爬使用不同策略
- 数据一致性要保证 - 使用事务或唯一索引避免重复
这个架构目前已经稳定运行8个月,日均处理请求量超过500万,数据完整率达到99.97%。后续我们计划加入智能调度算法,根据网站响应速度动态调整爬取频率。
