上一篇用 Scrapy 实现了单机爬虫,能爬几千页没问题。但当数据量达到几十万、上百万页时,单机就有瓶颈了——爬取速度不够、IP 容易被封、爬了一半挂了还要重新来。
用 Scrapy-Redis 做分布式,几台机器一起爬,速度翻倍,还自带断点续爬。
一、分布式爬虫的原理
单机 Scrapy 的请求队列在内存里,A 机器爬过的 B 机器不知道。分布式就是把请求队列放到 Redis 中,所有机器共享。
单机模式:
爬虫A → 内存队列 → 下载 → 解析
爬虫B → 内存队列 → 下载 → 解析
(各爬各的,互不通信)
分布式模式:
Redis 请求队列(所有爬虫共享)
↑↓ ↑↓ ↑↓
爬虫A 爬虫B 爬虫C
(请求被分配,不会重复爬)
核心优势:
- 爬取速度 = 机器数 × 单机速度(近似)
- 天然去重:Redis 集合保证同一个 URL 不会被爬两次
- 断点续爬:Redis 里的队列不会丢失,重启爬虫接着爬
二、安装与配置
1. 安装
pip install scrapy-redis
确保 Redis 已经安装并启动。
2. 配置 settings.py
# 使用 Scrapy-Redis 的调度器(替代默认的内存调度器)
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 使用 Redis 去重组件(替代默认的内存去重)
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# Redis 连接配置
REDIS_HOST = "localhost" # Redis 服务器地址
REDIS_PORT = 6379
REDIS_PARAMS = {
"password": "", # Redis 密码
"db": 0,
}
# 持久化请求队列(爬虫停了以后不清空)
SCHEDULER_PERSIST = True
# 爬虫停止后是否清空去重集合(建议 True,避免内存堆积)
SCHEDULER_FLUSH_ON_START = False
# 可选:从 Redis 中读取起始 URL
REDIS_START_URLS_AS_SET = True
3. 修改爬虫代码
# spiders/quotes.py
from scrapy_redis.spiders import RedisSpider
# 原来继承 scrapy.Spider,现在继承 RedisSpider
class QuotesSpider(RedisSpider):
name = "quotes"
# 不再写 start_urls,而是从 Redis 读取
redis_key = "quotes:start_urls"
def parse(self, response):
# 解析逻辑和单机时完全一样
for quote in response.css("div.quote"):
yield {
"text": quote.css("span.text::text").get(),
"author": quote.css("small.author::text").get(),
}
# 翻页
next_page = response.css("li.next a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)
关键区别: 继承从 scrapy.Spider 改为 scrapy_redis.spiders.RedisSpider,不再写 start_urls,改为 redis_key。
三、启动分布式爬虫
第一步:启动爬虫(所有机器都执行)
# 两台机器上都执行,它们都会去连同一个 Redis
scrapy crawl quotes
此时爬虫启动后会等待——因为 Redis 里还没有起始 URL。
第二步:向 Redis 推送起始 URL
在任意一台机器上执行:
# 向 Redis 的 quotes:start_urls 集合中插入起始 URL
redis-cli lpush quotes:start_urls "https://quotes.toscrape.com"
推入后,所有等待中的爬虫会立刻开始爬取,自动分配请求,不会重复。
第三步:随时追加新任务
redis-cli lpush quotes:start_urls "https://quotes.toscrape.com/page/2/"
新 URL 会被所有爬虫共享,继续爬取。
四、Redis 中的数据查看
# 查看等待爬取的请求数量
redis-cli llen quotes:requests
# 查看已爬取的 URL 数量(去重集合)
redis-cli scard quotes:dupefilter
# 查看爬取结果(如果配置了 ITEM_PIPELINES)
redis-cli llen quotes:items
核心 Redis key 说明:
| Key | 类型 | 说明 |
|---|---|---|
爬虫名:requests | List/ZSet | 待爬取的请求队列 |
爬虫名:dupefilter | Set | 已爬取的 URL(去重用) |
爬虫名:items | List | 爬取到的数据(如果启用了 RedisPipeline) |
爬虫名:start_urls | List/Set | 起始 URL 入口 |
五、数据存储——分布式写入
数据量大了以后,每台机器各自存一份 JSON 显然不行。统一存到 MongoDB 或 MySQL。
# pipelines.py
import pymongo
class MongoPipeline:
def open_spider(self, spider):
# 所有爬虫都连同一个 MongoDB
self.client = pymongo.MongoClient("localhost", 27017)
self.db = self.client["spider_data"]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
# 插入到同一张表
self.db["quotes"].insert_one(dict(item))
return item
# settings.py
ITEM_PIPELINES = {
# 注释掉默认的 JsonPipeline,换成 MongoPipeline
"myspider.pipelines.MongoPipeline": 300,
}
关键: 所有爬虫连接同一个数据库,数据自动汇总。
六、实战:分布式爬取豆瓣电影 Top250
from scrapy_redis.spiders import RedisSpider
class DoubanSpider(RedisSpider):
name = "douban"
redis_key = "douban:start_urls"
def parse(self, response):
# 提取电影列表
for movie in response.css("div.item"):
yield {
"title": movie.css("span.title::text").get(),
"rating": movie.css("span.rating_num::text").get(),
"quote": movie.css("span.inq::text").get(),
"url": movie.css("a::attr(href)").get(),
}
# 翻页
next_page = response.css("span.next a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)
启动:
# 机器A
scrapy crawl douban
# 机器B
scrapy crawl douban
# 推送起始 URL
redis-cli lpush douban:start_urls "https://movie.douban.com/top250"
两台机器同时爬,一台爬第 1 页、第 3 页、第 5 页……另一台爬第 2 页、第 4 页、第 6 页……谁也抢不到谁的任务。
七、分布式爬虫注意事项
1. IP 被封问题
多台机器用同一个 IP 访问同一个网站,被封的概率也翻倍了。必须配代理:
# middlewares.py
class ProxyMiddleware:
def process_request(self, request, spider):
# 从代理池获取一个代理
request.meta["proxy"] = "http://代理IP:端口"
建议每台机器配不同的代理 IP 池。
2. 爬取速度控制
# settings.py
# 每台机器各自限速,总的爬取速度会累加
DOWNLOAD_DELAY = 1.0 # 单机间隔 1 秒
CONCURRENT_REQUESTS = 8 # 单机并发 8
# 3 台机器总速度 ≈ 3 × 8/1 = 24请求/秒
3. 去重机制
Scrapy-Redis 的去重是基于 URL 的,如果两个不同的 URL 返回相同的内容,不会被去重。如果要基于内容去重,需要自定义 DupeFilter。
4. 爬虫挂了怎么办
# settings.py
# 调度器持久化 = 爬虫停了队列还在 Redis 中
SCHEDULER_PERSIST = True
# 重新启动爬虫
scrapy crawl douban
# 它会继续消费 Redis 中剩余的请求,不会从头开始
八、什么时候需要分布式?
| 数据量 | 推荐方案 | 原因 |
|---|---|---|
| < 1万页 | 单机 Scrapy | 几十分钟的事,没必要分布式 |
| 1万~10万页 | 单机 Scrapy + 优化配置 | 加大并发、减小延迟就能搞定 |
| 10万~100万页 | 分布式 Scrapy-Redis | 单机需要跑几天,扛不住 |
| > 100万页 | 分布式 + 消息队列 | 需要更健壮的架构 |
总结
Scrapy-Redis 只是把请求队列从内存搬到 Redis,代码改动很小——改继承、删 start_urls、加三行配置,单机就变分布式了。
单机爬虫 → 加 scrapy-redis 配置 → 多台机器启动 → push 起始 URL → 分布式跑起来
建议先在一台机器上把爬虫调好,再部署到多台机器上。不要还没调通就上分布式,排查问题会多一倍的复杂度。
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

1392

被折叠的 条评论
为什么被折叠?



