分布式爬虫是解决单台机器爬虫效率低下、爬取范围有限、容易被反爬限制等问题的核心方案,而Scrapy+Redis的组合凭借其轻量、高效、易扩展的特性,成为分布式爬虫开发的主流选择。本文将从入门基础到实战优化,全面讲解如何基于 Scrapy 和 Redis 搭建稳定、高效的分布式爬虫系统。
一、核心原理:为什么 Scrapy+Redis 能实现分布式?
Scrapy 本身是一个强大的单机爬虫框架,但它的调度器、队列存储都运行在单台机器上,无法直接支持分布式协作。而 Redis 作为一款高性能的键值对数据库,具备分布式共享、高并发读写、数据持久化的特性,恰好能弥补 Scrapy 的分布式短板,二者的协作核心围绕以下 3 点展开:
- 共享请求队列(Request Queue):打破单机 Scrapy 的本地请求队列限制,将所有待爬取的 URL 请求统一存储在 Redis 的队列(通常使用 List 或 ZSet 数据结构)中,所有分布式节点都可以从这个公共队列中获取待爬取任务。
- 共享去重集合(Duplication Filter) :Scrapy 单机的去重依赖本地内存的
RFPDupeFilter,分布式环境下无法跨节点共享去重信息。借助 Redis 的 Set 数据结构,将所有已爬取或已入队的 URL 进行哈希存储,实现跨节点的全局去重,避免重复爬取。 - 分布式节点协作:多个 Scrapy 爬虫节点(可以是同一台机器的多个进程,也可以是不同服务器)共用 Redis 的请求队列和去重集合,各自独立完成 "取任务→爬取数据→解析响应→生成新请求→存入公共队列" 的流程,实现任务的分布式分发和并行执行。
简单来说:Redis 充当了分布式爬虫的 "中央调度中心",而多个 Scrapy 节点则是 "执行工人",二者配合实现高效的分布式数据爬取。
二、入门准备:环境搭建与依赖安装
在开始编码之前,需要完成基础环境的搭建和相关依赖的安装,确保整个分布式系统能够正常运行。
1. 基础环境要求
- Python 3.7+(推荐 3.8~3.10,兼容性更好)
- Redis 服务(本地或远程,推荐 5.0+,开启持久化避免任务丢失)
- 操作系统:Windows/Linux/macOS(生产环境优先 Linux)
2. 核心依赖安装
需要安装 Scrapy 框架和 Scrapy-Redis 扩展(该扩展已封装好 Redis 与 Scrapy 的协作逻辑,无需重复造轮子),执行以下 pip 命令即可:
bash
运行
# 安装Scrapy框架
pip install scrapy
# 安装Scrapy-Redis扩展(核心分布式依赖)
pip install scrapy-redis
3. Redis 服务配置(关键)
- 安装 Redis 后,启动 Redis 服务(本地测试可直接默认配置,生产环境需优化配置):
- Linux/macOS:
redis-server - Windows:双击
redis-server.exe(或通过命令行启动)
- Linux/macOS:
- 验证 Redis 服务是否可用:执行
redis-cli进入客户端,输入ping,返回PONG即表示服务正常。 - 生产环境额外配置:开启 Redis 持久化(RDB+AOF 混合持久化)、设置密码、限制 IP 访问,避免任务数据丢失和安全风险。
三、实战入门:搭建第一个分布式爬虫
本部分将以爬取公开测试站点(示例站点:http://quotes.toscrape.com)为例,一步步搭建分布式爬虫,核心步骤包括创建 Scrapy 项目、修改配置文件、编写爬虫逻辑、启动分布式节点。
步骤 1:创建 Scrapy 项目和爬虫
首先通过 Scrapy 命令创建项目和基础爬虫,执行以下命令:
bash
运行
# 创建Scrapy项目(项目名:distributed_spider)
scrapy startproject distributed_spider
# 进入项目目录
cd distributed_spider
# 创建爬虫(爬虫名:quotes_spider,目标站点:http://quotes.toscrape.com)
scrapy genspider quotes_spider quotes.toscrape.com
步骤 2:修改 Scrapy 配置文件(settings.py)
这是实现分布式的核心配置步骤,需要替换 Scrapy 的默认调度器、去重器,配置 Redis 连接信息,具体修改如下:
python
运行
# 1. 开启项目的爬虫中间件和下载中间件(默认已开启,确保注释未打开)
SPIDER_MIDDLEWARES = {
'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 500,
}
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}
# 2. 替换默认调度器为Scrapy-Redis的分布式调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 3. 替换默认去重器为Scrapy-Redis的分布式去重器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 4. 配置Redis连接信息(本地Redis可默认,远程Redis需填写IP、端口、密码)
REDIS_URL = None # 优先使用该配置,格式:redis://:password@host:port/db
REDIS_HOST = 'localhost' # Redis服务器IP
REDIS_PORT = 6379 # Redis服务器端口
REDIS_PASSWORD = '' # Redis服务器密码(无密码则留空)
REDIS_DB = 0 # 使用的Redis数据库编号(默认0)
# 5. 关键配置:爬虫关闭后是否保留Redis中的请求队列和去重集合
# True:保留,支持断点续爬(分布式场景推荐开启)
# False:不保留,爬虫关闭后清空Redis中的相关数据
SCHEDULER_PERSIST = True
# 6. 配置Scrapy-Redis的请求队列类型(可选,默认使用List)
# 可选值:scrapy_redis.queue.SpiderQueue(先进先出)、scrapy_redis.queue.SpiderStack(后进先出)、scrapy_redis.queue.SpiderPriorityQueue(优先级队列)
SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
# 7. 关闭Robots协议(测试场景开启,生产环境需谨慎)
ROBOTSTXT_OBEY = False
# 8. 配置下载延迟(避免给目标站点造成压力,防止被反爬)
DOWNLOAD_DELAY = 1
# 9. 配置并发数(根据目标站点抗压能力和自身服务器性能调整)
CONCURRENT_REQUESTS = 16
CONCURRENT_REQUESTS_PER_DOMAIN = 8
步骤 3:编写爬虫逻辑(quotes_spider.py)
修改自动生成的爬虫文件,实现数据爬取、解析和新请求生成,核心是让爬虫从 Redis 队列中获取任务,并将新生成的请求存入 Redis 队列,具体代码如下:
python
运行
import scrapy
from scrapy_redis.spiders import RedisSpider # 替换默认Spider为RedisSpider
# 注意:继承的是RedisSpider,而非默认的scrapy.Spider
class QuotesSpider(RedisSpider):
name = 'quotes_spider' # 爬虫名称,必须唯一
allowed_domains = ['quotes.toscrape.com'] # 允许爬取的域名
# 关键修改:移除start_urls,添加redis_key(分布式爬虫的任务入口,从Redis的该key对应的队列中获取起始URL)
redis_key = 'quotes:start_urls' # 格式:爬虫名:start_urls(自定义,需与后续入队命令对应)
def parse(self, response):
"""
解析响应数据,提取目标信息,生成新请求
"""
# 1. 提取页面中的名言数据(示例:提取文本和作者)
quotes = response.xpath('//div[@class="quote"]')
for quote in quotes:
yield {
'text': quote.xpath('.//span[@class="text"]/text()').extract_first(),
'author': quote.xpath('.//small[@class="author"]/text()').extract_first(),
}
# 2. 提取下一页URL,生成新的请求(自动存入Redis公共队列)
next_page = response.xpath('//li[@class="next"]/a/@href').extract_first()
if next_page:
# 拼接完整URL
next_page_url = response.urljoin(next_page)
# 生成新请求,交由Scrapy调度(自动存入Redis队列,实现分布式分发)
yield scrapy.Request(
url=next_page_url,
callback=self.parse,
dont_filter=False # 不跳过去重(默认False,正常参与分布式去重)
)
步骤 4:启动分布式爬虫(核心操作)
分布式爬虫的启动分为两步:向 Redis 中存入起始 URL 、启动多个 Scrapy 爬虫节点,具体操作如下:
1. 向 Redis 存入起始 URL
通过redis-cli客户端向配置的redis_key(即quotes:start_urls)中存入起始 URL,执行以下命令:
bash
运行
# 1. 进入Redis客户端
redis-cli
# 2. 存入起始URL(lpush命令:向List队列左侧添加数据,对应Scrapy-Redis的先进先出队列)
lpush quotes:start_urls http://quotes.toscrape.com
# 3. 验证是否存入成功(可选)
lrange quotes:start_urls 0 -1
2. 启动多个 Scrapy 爬虫节点
打开多个终端(或不同服务器),进入项目目录,执行相同的爬虫启动命令,即可启动多个分布式节点:
bash
运行
# 启动爬虫节点(每个终端执行一次,即可启动一个节点,支持无限扩展)
scrapy crawl quotes_spider
此时可以观察到:多个爬虫节点会从 Redis 的公共队列中获取任务,并行爬取数据,且不会出现重复爬取的情况,这就是一个最简单的分布式爬虫系统。
四、进阶优化:提升分布式爬虫的稳定性和效率
入门级的分布式爬虫能够正常运行,但在面对大规模爬取、反爬严格的站点时,还存在效率低下、容易崩溃、被封禁等问题,以下是核心优化方案:
1. 队列优化:使用优先级队列应对复杂爬取场景
默认的 List 队列(先进先出)无法满足有优先级的爬取需求(例如:优先爬取重要页面),Scrapy-Redis 支持优先级队列(SpiderPriorityQueue),只需修改settings.py中的队列配置:
python
运行
# 配置优先级队列(基于Redis的ZSet数据结构实现)
SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
在生成请求时,通过priority参数设置优先级(数值越大,优先级越高):
python
运行
yield scrapy.Request(
url=next_page_url,
callback=self.parse,
priority=10 # 设置优先级,高优先级任务优先被爬取
)
2. 去重优化:提升去重效率,减少 Redis 内存占用
默认的去重方案是将 URL 直接哈希后存入 Redis 的 Set 中,当 URL 数量巨大时,会占用大量 Redis 内存,且去重效率下降,可通过以下方式优化:
-
缩短哈希值长度 :Scrapy-Redis 默认使用
sha1哈希算法(生成 40 位十六进制字符串),可自定义去重类,使用更精简的哈希算法(如md5,32 位),减少内存占用。 -
分段去重 + 过期清理 :对于超大规模爬取,可将去重集合按时间或批次分段存储,并为 Redis 中的去重键设置过期时间,避免无效数据堆积:
python
运行
# 在settings.py中配置去重键的过期时间(自定义去重类实现) DUPEFILTER_EXPIRE = 86400 # 24小时过期,自动清理无效去重数据 -
避免无效 URL 入队:在生成新请求前,先对 URL 进行预处理(如去重、过滤无效参数、标准化格式),减少不必要的请求入队,减轻 Redis 压力。
3. 反爬优化:降低被封禁的风险
分布式爬虫并行爬取容易被目标站点识别并封禁 IP,这是实战中最需要解决的问题,核心优化方案如下:
-
设置随机 User-Agent :替换默认的 User-Agent,模拟不同浏览器请求,可使用
fake-useragent库:bash
运行
# 安装fake-useragent pip install fake-useragent在
settings.py中配置:python
运行
from fake_useragent import UserAgent UA = UserAgent() USER_AGENT = UA.random # 随机生成User-Agent -
使用代理 IP 池:搭建或接入第三方代理 IP 池(如阿布云、快代理),通过下载中间件实现代理 IP 的随机切换,避免单一 IP 被封禁。
-
优化爬取频率 :合理调整
DOWNLOAD_DELAY(下载延迟)、CONCURRENT_REQUESTS(并发数)、CONCURRENT_REQUESTS_PER_DOMAIN(单域名并发数),模拟人类浏览行为,避免高频请求。 -
启用 Cookies 池:对于需要登录或验证 Cookies 的站点,搭建 Cookies 池,定期更新 Cookies,避免因 Cookies 失效导致爬取失败。
4. 性能优化:提升爬取效率,降低资源消耗
- Redis 性能优化 :
- 生产环境使用 Redis 集群或哨兵模式,提高 Redis 的可用性和并发处理能力。
- 关闭 Redis 的不必要功能(如持久化中的 AOF 实时同步,改为每秒同步),减少 Redis 的 IO 压力。
- 合理选择 Redis 的数据结构,避免大 key(单个 key 存储过多数据),提升读写效率。
- Scrapy 自身优化 :
-
开启异步下载:Scrapy 默认使用异步框架 Twisted,确保
DOWNLOAD_HANDLERS中开启异步处理(默认已开启)。 -
关闭无用的日志:在
settings.py中设置日志级别为INFO或WARNING,减少日志 IO 消耗:python
运行
LOG_LEVEL = 'INFO' -
使用
Item Pipeline批量存储数据:避免每条数据都写入数据库(如 MySQL、MongoDB),而是批量收集后一次性写入,减少数据库连接开销。
-
- 节点优化 :
- 合理分配节点数量:根据目标站点的抗压能力和自身服务器带宽、CPU 性能,选择合适的节点数量,避免过度并发导致节点自身崩溃。
- 负载均衡:使用云服务器搭建分布式节点时,开启负载均衡,避免单个节点任务过重。
5. 监控与容错优化:保障系统稳定运行
-
Redis 监控 :使用
redis-cli info命令或第三方工具(如 Redis Insight、Prometheus+Grafana)监控 Redis 的内存使用、队列长度、读写频率,及时发现异常。 -
爬虫节点监控:监控每个爬虫节点的运行状态、CPU / 内存占用、爬取速度,当节点崩溃时,自动重启节点。
-
断点续爬与故障恢复 :依赖
SCHEDULER_PERSIST = True配置,保留 Redis 中的请求队列和去重集合,当节点崩溃或服务器宕机后,重启节点即可继续爬取,无需重新开始。 -
异常处理 :在爬虫的
parse方法中添加异常捕获逻辑,处理 404、500 等 HTTP 错误,避免单个请求失败导致整个爬虫节点崩溃:python
运行
def parse(self, response): try: # 正常解析逻辑 pass except Exception as e: self.logger.error(f"解析失败:{response.url},异常信息:{str(e)}") # 可选:将失败的URL重新存入Redis队列,待后续重试 yield scrapy.Request(url=response.url, callback=self.parse, dont_filter=True, priority=-1)
五、总结
Scrapy+Redis 是实现分布式爬虫的黄金组合,其核心是通过 Redis 实现请求队列和去重集合的分布式共享,让多个 Scrapy 节点协同工作,大幅提升爬取效率。
本文从入门基础出发,讲解了环境搭建、核心配置、实战编码和启动流程,再通过进阶优化方案解决大规模爬取中的稳定性、效率、反爬等问题。需要注意的是,分布式爬虫的开发并非一蹴而就,在实战中需要根据目标站点的特性、爬取需求,灵活调整配置和优化方案。
同时,爬取数据时必须遵守目标站点的robots.txt协议(生产环境),尊重网站的版权和使用条款,避免非法爬取和恶意攻击,做一个合规的爬虫开发者。