Python 爬虫 Scrapy:分布式抓取框架
Scrapy 分布式抓取完全指南
本教程将带你从零开始,掌握基于 Scrapy 的分布式抓取技术。你将了解分布式爬虫的核心原理、使用 Scrapy-Redis 改造单机爬虫、并通过真实案例实现多节点协同抓取。
为什么要使用分布式抓取
单机 Scrapy 受限于带宽、CPU 和 IP 资源,面对海量数据时效率低下。分布式抓取能带来三大核心优势:
- 高并发:多台机器同时发起请求,吞吐量成倍提升。
- 易扩展:随时增加工作节点,无需修改代码。
- 去重稳定:利用 Redis 集合全局过滤重复 URL,避免重复抓取。
环境准备
在开始之前,请确保所有节点均安装以下组件:
- Python 3.7+
- Scrapy 2.5+
- Redis 6.0+
- scrapy-redis 库
pip install scrapy scrapy-redis redis
建议在每台工作节点上使用相同的 Python 环境和项目代码,可通过 Git 或 NFS 同步项目。
分布式抓取原理
Scrapy 默认的引擎、调度器、去重器都运行在单进程内存中。要实现分布式,需要将请求队列和去重指纹共享到所有节点都能访问的地方。Redis 因其原子操作和高性能,成为实现这一目标的最佳选择。
核心架构:
- 调度器:使用 Redis 的 List 或 Zset 存储待抓取请求,所有节点从中获取。
- 去重器:使用 Redis 的 Set 保存已抓取 URL 的指纹。
- 数据管道:可选地将 Item 统一存入 Redis 或数据库,避免结果分散。
所有爬虫节点都作为消费者,从同一个 Redis 队列中获取任务,实现协同工作。
使用 Scrapy-Redis 构建分布式爬虫
scrapy-redis 是一个封装好的组件,提供了分布式所需的调度器、去重器和可继承的 Spider。
创建项目与基础爬虫
首先创建一个普通的 Scrapy 项目:
scrapy startproject dist_crawler
cd dist_crawler
scrapy genspider example example.com
安装 scrapy-redis 并修改 settings.py
在 settings.py 中添加或修改以下配置:
# 开启 scrapy-redis 调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 使用 scrapy-redis 去重类
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 允许暂停,Redis 请求记录不丢失
SCHEDULER_PERSIST = True
# 指定 Redis 连接信息(默认连接 localhost:6379)
REDIS_HOST = '你的Redis服务器IP'
REDIS_PORT = 6379
# REDIS_PARAMS = {'password': 'your_password'} # 如有密码
改造 Spider 为分布式 Spider
打开 spiders/example.py,将 Spider 的继承类改为 RedisSpider,并指定 redis_key:
import scrapy
from scrapy_redis.spiders import RedisSpider
class ExampleSpider(RedisSpider):
name = 'example'
redis_key = 'example:start_urls' # Redis 中存放起始 URL 的键
def parse(self, response):
# 提取需要的数据,生成 Item
for item in self.parse_item(response):
yield item
# 提取新的链接,生成请求
for next_url in response.css('a::attr(href)').getall():
yield scrapy.Request(url=response.urljoin(next_url), callback=self.parse)
RedisSpider不再使用start_urls,启动后它会监听 Redis 中的example:start_urls列表,等待任务推入。
启动种子 URL
在任意一台能连接 Redis 的机器上,向 example:start_urls 推入一个起始 URL:
redis-cli lpush example:start_urls "http://example.com"
运行爬虫
在所有工作节点上执行相同的命令:
scrapy crawl example
每个节点都会从 Redis 队列中获取请求并处理,实现分布式抓取。
进阶配置
使用 Redis 优先级队列
默认调度器使用先进先出(FIFO)队列。如果需要优先抓取某些页面,可将 SCHEDULER_QUEUE_CLASS 改为优先级队列:
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
此时 Redis 使用 Zset 存储请求,通过控制权重来调整抓取顺序。
分布式数据处理
Item 也可直接写入 Redis,供后续统一清洗入库。在 pipelines.py 中使用 RedisPipeline:
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300,
}
Item 会被存储到 Redis 的 项目名:items 列表中,再用独立脚本批量消费。
Scrapyd 集群部署
为了方便管理多节点上的爬虫任务,可以使用 Scrapyd。在每台机器上安装并启动 Scrapyd:
pip install scrapyd
scrapyd
然后通过 scrapyd-deploy 将项目部署到所有节点,最后通过 API 或 ScrapydWeb 等工具统一调度。
集中调度示例:
curl http://node1:6800/schedule.json -d project=dist_crawler -d spider=example
curl http://node2:6800/schedule.json -d project=dist_crawler -d spider=example
各节点仍共享同一个 Redis,协同抓取。
完整示例:分布式抓取书籍信息
下面以一个抓取在线图书信息的分布式爬虫为例,展示完整流程。
items.py
import scrapy
class BookItem(scrapy.Item):
title = scrapy.Field()
price = scrapy.Field()
url = scrapy.Field()
spiders/books.py
from scrapy_redis.spiders import RedisSpider
from dist_crawler.items import BookItem
class BooksSpider(RedisSpider):
name = 'books'
redis_key = 'books:start_urls'
def parse(self, response):
for book in response.css('article.product_pod'):
item = BookItem()
item['title'] = book.css('h3 a::attr(title)').get()
item['price'] = book.css('p.price_color::text').get()
item['url'] = book.css('h3 a::attr(href)').get()
yield item
next_page = response.css('li.next a::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)
推入种子:
redis-cli lpush books:start_urls "http://books.toscrape.com/"
在多个终端分别运行 scrapy crawl books 即可看到分布式抓取效果。
常见问题与优化
1. 单节点空跑,CPU 占用高
Redis 队列为空时,爬虫会进入忙等状态。可通过修改爬虫的 custom_settings,或使用 SCHEDULER_IDLE_BEFORE_CLOSE 设置空闲等待时间后自动关闭。
SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 空闲10秒后关闭爬虫
2. Redis 内存压力
请求队列和去重集合会不断增长。建议:
- 使用 Redis 的
maxmemory策略,如volatile-lru。 - 定期清理已完成的去重集合(但需谨慎,避免重复抓取)。
3. 反爬应对
分布式爬虫更容易触发目标网站的反爬策略。应做好:
- 为不同节点配置不同的 User-Agent 和 IP。
- 使用下载中间件添加随机延迟。
- 引入代理 IP 池,在中间件中动态更换。
4. 数据一致性
如果多个节点同时写入数据库,需考虑并发控制。建议先将数据写入 Redis 或 Kafka,再用单消费者写入最终数据库。
总结
Scrapy-Redis 让你能够快速将单机爬虫改造为强大的分布式系统。核心思路是共享 Redis 中的请求队列和去重集合,使多个爬虫实例协同工作。结合 Scrapyd 和良好的反爬策略,你可以构建出高可用、可扩展的数据采集平台。
现在,尝试用本教程的内容改造你的第一个分布式爬虫吧!