Scrapy+Redis分布式爬虫实战:调度器、去重与请求队列设计

发布时间:2026/9/14 4:34:41
Scrapy+Redis分布式爬虫实战:调度器、去重与请求队列设计 简介这是一套面向毕业设计场景的Python分布式爬虫源码案例核心围绕Scrapy框架与Redis中间件展开适合正在做爬虫方向课设或毕设的学生也适合希望掌握分布式采集思路的开发者。压缩包内共14个文件包含7个py源码、4个pyc编译文件、scrapy.cfg配置、README说明文档等整体约9KB目录结构精简便于快速阅读和二次修改。代码重点演示了如何借用Redis的队列与去重能力让Scrapy爬虫在多机或多任务之间协同工作读者可以从中理解分布式调度、URL去重和请求队列管理的基本原理。同时源码提供了从项目配置到爬虫实现的可运行骨架可用作毕业设计原型或课程设计参考帮助节省从零搭建的时间。这套代码已有534人学习下载对于正要设计分布式爬虫项目的同学是高性价比的借鉴素材。1. 一个被误解的“分布式爬虫”ScrapyRedis到底解决了什么很多人以为把 Scrapy 项目复制到多台机器上然后分别启动 spider 就是分布式爬虫这是一个很昂贵的误会。单机 Scrapy 的调度队列、去重集合、请求指纹都存在内存里每台机器各抓各的重复请求谁也没办法发现数据量一上来还会把文件并发耗尽。这个基于 Scrapy Redis 的毕业设计项目真正解决的是把调度器和去重器从爬虫进程中剥离出来用 Redis 作为共享的调度中心和去重仓库让多个爬虫节点消费同一个请求队列。适合正在做分布式爬虫选型、准备搭企业级采集任务的工程师也适合用 Python 做课程设计的人拿它当一套能讲清楚原理的骨架。2. 拆解 scrapy-redis 的分布式骨架调度器、去重与请求队列2.1 单机 Scrapy 爬虫的真正瓶颈是调度状态运行一个普通的 Scrapy 爬虫时Scheduler 和 DupeFilter 都驻留在进程内。Scheduler 用一个优先队列维护待抓取请求DupeFilter 用 Python 的集合类记录已访问的 URL 指纹。单机场景下这套机制没问题但一旦部署到多台机器每个进程都有自己独立的队列同一个 URL 会被多台机器重复抓取。更麻烦的是如果某台机器中途崩溃它内存里还没有抓取的请求直接丢掉了没有持久化追回这些请求只能靠重新爬。把调度状态放到 Redis 上是一个成熟的解法。scrapy-redis 重写了 Scrapy 的调度器用 Redis 的 List 或者 ZSet 存储待抓取请求用 Set 存储请求指纹这样所有节点看到的是同一份调度状态。某个节点挂了其他节点可以从 Redis 里继续取出它没完成的请求不需要重新发现种子 URL。这种架构下的爬虫不是“多台机器跑相同代码”而是“一个逻辑爬虫由多台机器协同执行”。2.2 Redis 在分布式爬虫中的三个角色调度队列、去重集合、结果暂存先梳理清楚 Redis 在这个项目里具体扮演什么角色。调度队列是核心scrapy-redis 默认用 Redis List 的 lpush 和 rpop 实现先进先出队列保证每个请求只被某个节点取走。去重集合是另一个关键点请求的 URL 经过指纹计算后写入 Redis Set判断是否已经抓过时直接通过 sadd 和 sismember 完成。第三层是结果暂存爬虫抓取到的 item 可以先序列化后写入 Redis List然后由后续的 pipeline 或者消费者异步写入数据库这样即使数据库正在抖动数据也不会丢在内存里。去重指纹的计算用的是 Scrapy 自带的 RequestFingerprint它基于请求的方法、URL、请求头和 body 生成一个 40 位十六进制字符串。这种指纹模型有一个边界它不关心响应内容是否是 404 页面所以如果目标网站对不存在的 URL 也返回 200指纹去重就无法识别出这些无效抓取。实际使用中我会在 pipeline 里增加一层校验比如根据 HTTP 状态码和内容长度决定是否需要保留这个 item。2.3 一个最小可运行的 scrapy-redis 配置长什么样在项目根目录的 settings.py 里分布式相关的配置其实只有几行。关键参数是替换掉默认调度器和去重过滤器。SCHEDULER scrapy_redis.scheduler.Scheduler DUPEFILTER_CLASS scrapy_redis.dupefilter.RFPDupeFilter REDIS_URL redis://127.0.0.1:6379/0 SCHEDULER_PERSIST True第一行把 Scrapy 默认的调度器换成 scrapy-redis 的调度器第二行把去重过滤器换成基于 Redis Set 的实现。第三行指定 Redis 连接地址这个 URL 格式包含协议、主机、端口和数据库编号。第四行 SCHEDULER_PERSIST 表示爬虫正常关闭后不清空 Redis 里的请求队列和去重集合这样下次启动可以从上次中断的位置继续。如果设置成 False爬虫结束时队列会被清掉失去断点续爬能力。要注意的是redis 和 scrapy 的版本搭配不能太随意。当前 scrapy-redis 0.7.0 版本对 Redis 客户端有要求。一般我会用 redis 3.x太新的 5.x 版本在连接池参数上会出现兼容性警告导致调度器实例化时后台刷出大量报错信息。虽然不影响主要功能但排查问题时会干扰视野。3. 从源码包看懂项目结构book 模块、scrapy.cfg 与配置项3.1 源码包里的文件到底在干什么这个毕业设计包里有一个 book 目录看起来是一个围绕书籍信息抓取的爬虫项目。我拿到这种源码包时一般会先按文件职责做一次归类避免一上来就扎进抓取逻辑里。项目里有 code_111230、.gitattributes、book、scrapy.cfg、README.md 和两个隐藏资源文件。.gitattributes 控制 Git 的换行符和文件合并策略和爬虫逻辑无关但如果你在 Windows 上开发、Linux 上部署它会避免 CRLF 和 LF 混用引发的诡异报错。book 目录是核心代码scrapy.cfg 是项目级配置文件它告诉 Scrapy 去哪里找 settings。典型内容是把配置目录指向 book 模块。[settings] default book.settings [deploy] project bookscrapy.cfg 里的 default 参数指向实际的 Python 模块路径如果你的模块名不是 book发布或者调用时都会找不到配置。很多同学从网上下载源码后直接 scrapy crawl 报错大概率是这里没改。deploy 部分是 Scrapy 的远程部署配置本地跑不需要关注它。3.2 settings.py 里哪些参数决定了分布式行为打开 book/settings.py重点看 BOT_NAME、SPIDER_MODULES 和之前提到的调度器配置。BOT_NAME 要跟 scrapy.cfg 里的 project 保持一致否则启动时识别不到 spider 模块。SPIDER_MODULES 告诉 Scrapy 去哪里遍历 spider 类默认填项目模块名即可。另外还要注意 CONCURRENT_REQUESTS、DOWNLOAD_DELAY 这类参数的设置。分布式环境下这些参数是每个节点独立生效的也就是说如果你有 4 个节点每个节点并发 16整体就是 64 个并发请求。但这不意味着越大越快目标网站的压力池是共享的我见过把并发调到 32 后Redis 里待抓取队列持续堆积但是对方 CDN 开始大量返回 499 状态码因为单机节点的连接被服务端直接拒绝。Redis 连接参数也不能忽略。REDIS_URL 写死到某个节点的端口时要注意这个 Redis 是否允许外部访问。默认 Redis 只监听 127.0.0.1其他节点无法连入。我一般会在配置里新增一个 REDIS_PARAMS 字段控制连接超时和 socket 超时避免某个节点网络抖动时Scheduler 的阻塞把整个爬虫卡死。3.3 items.py 与 pipelines.py 中的数据流设计items.py 定义的是抓取结果的结构这一步决定了后续数据落库的字段映射。一个简单的书籍 item 可以这样设计import scrapy class BookItem(scrapy.Item): title scrapy.Field() author scrapy.Field() isbn scrapy.Field() price scrapy.Field() detail_url scrapy.Field()字段类型用 scrapy.Field 而不是 Python 原生类型是为了让 Scrapy 的 ItemLoader 和序列化机制能够正常工作。如果你需要做数据校验可以在 Field 的字典里加 input_processor 和 output_processor这比在 pipeline 里手工判空要清晰得多。pipelines.py 里的核心逻辑是消费 spider 传过来的 item然后写入存储。在分布式场景中我推荐 pipeline 里只做 Redis 暂存。import redis class RedisWriterPipeline: def process_item(self, item, spider): self.r.rpush(book_item_queue, dict(item)) return item def open_spider(self, spider): self.r redis.Redis(host127.0.0.1, port6379, db1)rpush 把 item 转成字典后序列化到 Redis 的 List 尾部后续可以写一个独立的消费者从列表中取数据落库。这样做的好处是抓取速度和写入数据库的速度完全解耦数据库慢不会拖慢爬虫。注意 return item 不要遗漏否则后续 pipeline 接收不到这个 item。如果 pipeline 中出现异常需要在 process_item 里捕获并记录到日志而不是直接抛出否则整个请求会被标记失败并且重试造成脏数据重复写入。4. 实战把这份毕业设计跑起来并压测分布式抓取4.1 环境准备Python、Redis、Scrapy 的版本搭配这份源码基于 Python 环境建议使用 Python 3.8 到 3.10。太高版本的 Python 在部分系统镜像里找不到预编译的 lxml 包得自己编。安装依赖时用 requirements.txt 或 pip 手动安装。pip install scrapy2.5.1 pip install scrapy-redis pip install redis3.5.3如果不指定 scrapy 版本最新版可能会跟 scrapy-redis 的 API 出现不兼容。Redis 服务端方面如果你用 Windows可以直接找 redis windows 下载包解压后运行 redis-server.exe如果你在 Linux 上用 docker 安装 redis 主从会更干净。一个适合开发的实例这样起docker run -d --name redis-dev -p 6379:6379 redis:6.2参数 -d 表示后台运行--name 指定容器名-p 把容器 6379 端口映射到本机 6379redis:6.2 是镜像标签。启动后可以用 redis-cli ping 检查连接。4.2 启动 Redis 服务与配置检查在运行爬虫之前先确认 Redis 的数据类型和连接是否正常。使用 redis-cli 进入命令行执行 dbsize 看当前数据库的 key 数量。如果连接失败多半是 bind 配置项限制或端口被防火墙挡了。开发环境里可以把 Redis 配置文件里的 bind 127.0.0.1 注释掉并设置 protected-mode no生产环境不建议这样做。检查配置时要注意 settings.py 里 REDIS_URL 使用的数据库编号。redis 默认有 16 个 dbdb0 存调度队列和去重集合db1 存 item 数据两者隔离会方便观测。如果都混在 db0LLEN 和 SCARD 查出来的数据无法区分到底属于哪一类排查问题时会绕弯路。4.3 运行爬虫时的命令与参数追踪在项目根目录直接运行启动命令scrapy crawl book_spider注意这个命令会阻塞在当前节点。想要观察调度队列变化需要另开一个终端连接 Redis。假设队列 key 是 spider 的爬虫名字加 requests默认是 book_spider:requests那么可以用 LLEN 查看队列长度用 ZCARD 查看去重集合长度。再看一个容易被忽略的参数SCHEDULER_PERSIST 打开之后爬虫停止后 Redis 队列不会自动清除。如果你改了抓取逻辑想要重新全量抓取需要先清空 keyredis-cli del book_spider:requests book_spider:dupefilterdel 后面可以跟多个 key空格分隔。这两个 key 不清理新旧指纹混在一起你会发现新改了规则但请求发不出去因为从第一步就去重掉了。这个坑在毕业设计答辩时经常被问到。4.4 分布式抓取中最常见的三个故障与排查第一个故障是爬虫启动后所有节点都卡住不动。打开 Redis 的 MONITOR 模式输入 redis-cli monitor看当前是否有命令进入。如果没有任何操作一般是 REDIS_URL 连接失败scrapy-redis 在初始化时不会立刻报错而是等第一个请求调度时才抛出连接异常。第二个故障是请求被重复抓取检查 DUPEFILTER_CLASS 是否被设置成默认的 scrapy.dupefilter如果没改过来每个节点独立去重分布式就失效了。第三个故障是 Redis 内存快速上涨用 redis-cli --bigkeys 找到体积最大的 key通常是 item 队列积压。说明消费端处理速度跟不上生产端这时要降低 CONCURRENT_REQUESTS并增加独立的 item 消费者而不是继续堆节点。5. 用 Redis 命令验证去重效果和节点扩展的边界5.1 通过 Redis 实时观察爬虫队列状态把 Redis 当作分布式爬虫的大脑之后你会发现自己开始习惯用 redis-cli 而不是爬虫日志来观察系统。抓取过程中最常用的几条命令是 LLEN book_spider:requests 看待抓取队列SCARD book_spider:dupefilter 看已经抓取的指纹数以及 LRANGE book_spider:requests 0 5 看队列头部请求。LLEN book_spider:requests SCARD book_spider:dupefilterLLEN 返回的是 List 的长度SCARD 返回的是 Set 的基数。当你看到 SCARD 持续增长而 LLEN 不再增加时说明爬虫已经进入尾期节点之间的协调同步也都验证成功了。5.2 验证 URL 去重手动往去重集合插入数据分布式爬虫最怕的是去重失效。验证去重是否生效可以手动构造一个指纹塞进 Redis 去重集合然后用相同 URL 的请求去跑观察是否被跳过。redis-cli sadd book_spider:dupefilter abc123 redis-cli sismember book_spider:dupefilter abc123sadd 命令返回 0 表示元素已存在返回 1 表示插入成功。sismember 用做判断时返回 1 代表指纹存在说明这个 URL 已经被认为抓取过。这个技巧可以在不改爬虫代码的前提下快速测试去重逻辑的边界比如对 URL 大小写、请求参数顺序不敏感的指纹如何处理。5.3 多节点扩展时注意 Redis 连接池与代理池当节点增加到 10 个以上Redis 连接池会成为一个隐蔽的瓶颈。scrapy-redis 的调度器每个节点都会保持多条连接默认连接池大小是 10。如果节点过多Redis 连接数会被占满表现为爬虫间歇性卡顿。这时需要在开启 Redis 服务的机器上修改 maxclients比如在 redis.conf 中设置 maxclients 2048并重启实例。代理池和 Redis 的配合也要重新考虑如果多节点共享同一个代理池代理分配效率会成为新的瓶颈此时我会把代理获取也封装成 Redis List由节点从列表中取代理用完再归还避免每次请求都重新认证。当 queue 堆到百万量级时还可以考虑把请求指纹从 Set 换成 Redis 的 HyperLogLog用 PFADD 降低内存占用代价是极低的误判率适合抓取后续不需要精确回查的页面。本文还有配套的精品资源点击获取