Scrapy 是一个用 Python 编写的高性能、可扩展的开源网络爬虫框架
Scrapy 是一个用 Python 编写的高性能、可扩展的开源网络爬虫框架,原生设计为单机架构,不直接支持分布式爬虫。但通过结合外部组件(如 Redis、RabbitMQ、Kafka 等),可构建分布式爬虫系统,常见方案包括:
✅Scrapy-Redis(最经典)
- 基于 Redis 实现请求队列(
SpiderQueue/PriorityQueue)、去重集合(DupeFilter)和指纹存储; - 多个 Scrapy 爬虫实例共享同一 Redis 数据库,实现任务分发与状态同步;
- 需重写
Scheduler、DupeFilter和ItemPipeline,继承scrapy_redis.spiders.RedisSpider。
✅Scrapy + Redis + 自定义调度器
- 利用 Redis 的
LPUSH/BRPOP实现任务队列; - 使用
scrapy-redis提供的中间件或自行开发RedisScheduler; - 支持动态添加起始 URL(通过
lpush myspider:start_urls http://xxx)。
⚠️ 注意事项:
- Scrapy 本身无内置分布式协调机制(如节点发现、故障转移、负载均衡),需依赖外部服务;
- 去重需统一使用 Redis 的
set或bloom filter(如scrapy-redis-bloom)避免重复抓取; - Item 存储建议统一写入数据库(MySQL/PostgreSQL)或消息队列(Kafka),避免多实例写冲突;
- 日志、监控、限速策略需集中管理(如 Prometheus + Grafana + ELK)。
📌 示例(Scrapy-Redis 快速启动):
pipinstallscrapy-redis在settings.py中配置:
SCHEDULER="scrapy_redis.scheduler.Scheduler"DUPEFILTER_CLASS="scrapy_redis.dupefilter.RFPDupeFilter"REDIS_URL='redis://localhost:6379'继承RedisSpider:
importscrapyfromscrapy_redis.spidersimportRedisSpiderclassMySpider(RedisSpider):name='myspider'redis_key='myspider:start_urls'defparse(self,response):yield{'url':response.url,'title':response.css('title::text').get()}启动多个实例后,向 Redis 推送 URL 即可触发分布式抓取。
Scrapy-Redis 的去重机制核心是将 Scrapy 原生的本地RFPDupeFilter(基于scrapy.utils.request.request_fingerprint)迁移到 Redis 中持久化存储,从而实现多爬虫实例共享同一去重集合。其本质不是重新设计指纹算法,而是复用 Scrapy 的指纹逻辑 + 分布式存储层。
✅指纹生成逻辑(与本地完全一致)
Scrapy-Redis 使用scrapy.utils.request.request_fingerprint()生成请求指纹(fingerprint),该函数默认行为为:
- 对请求的
method、url、body、headers(仅含标准化后的关键头,如User-Agent、Cookie等,取决于DUPEFILTER_DEBUG和HTTPCACHE_IGNORE_HEADERS配置)、cookies等字段进行规范化(如 URL 去参数顺序、body 排序、headers 小写归一化); - 序列化后经 SHA1 哈希,生成长度为 40 的十六进制字符串(如
'a1b2c3...'); - ✅该指纹算法与 Scrapy 默认
RFPDupeFilter完全相同,保证语义一致性——同一请求在单机或分布式环境下生成的指纹绝对一致。
✅去重存储与判断流程(分布式关键差异)
| 环节 | 本地RFPDupeFilter | Scrapy-RedisRFPDupeFilter |
|---|---|---|
| 存储介质 | Pythonset()(内存,进程级) | RedisSET或Bloom Filter(如scrapy-redis-bloom) |
| 去重范围 | 单个爬虫进程内 | 所有连接同一 Redis 的爬虫实例全局共享 |
| 判断方式 | fingerprint in self.fingerprints | SISMEMBER dupefilter:myspider <fingerprint>(原子操作) |
| 持久化 | 进程退出即丢失 | Redis 持久化(RDB/AOF)可长期保留去重记录 |
| 扩展性 | 无法跨实例共享 | 支持无限水平扩展爬虫节点 |
🔧可选增强:Bloom Filter 替代 SET
为节省内存(尤其海量 URL 场景),可启用scrapy-redis-bloom:
- 使用 Redis 的
bf.add/bf.exists(需 RedisBloom 模块); - 以极小概率误判(false positive,≈0.1% 可配置),但不产生 false negative(不会漏判重复);
- 内存占用仅为 SET 的 1/8~1/10,适合亿级 URL 去重。
⚠️ 注意事项:
- 若自定义
Request并覆盖__hash__或__eq__,需确保request_fingerprint()仍能正确反映语义唯一性; DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"是默认配置,无需修改即可生效;- Redis 连接异常时,
RFPDupeFilter默认会抛出异常(可捕获并降级为本地去重,但需自行实现容错逻辑)。
