如果你对网络数据抓取充满好奇,想从海量信息中提取有价值的内容,那么 Scrapy 绝对是你不可错过的利器!而当你需要处理大规模数据抓取,或者担心单机性能瓶颈时,Scrapy-Redis 更是你的不二之选,它能让你的爬虫具备分布式、可扩展的能力。
本篇博客将带你从零开始,深入理解 Scrapy 的执行流程,并手把手教你如何使用 Scrapy-Redis 构建一个分布式爬虫系统。即使你是编程小白,也能轻松掌握!
Scrapy 篇:单机爬虫的精妙艺术
首先,我们来认识 Scrapy,它是 Python 领域一个强大而高效的爬虫框架。它的设计理念是高度模块化,让你可以专注于数据提取和业务逻辑,而无需过多关注底层的网络请求、并发处理等细节。
Scrapy 的核心组件
理解 Scrapy 的执行流程,要从它的核心组件说起:
- Scrapy Engine(引擎):Scrapy 的核心,负责控制所有组件的数据流,并根据请求和响应进行调度。它就像一个指挥官,协调各个部件协同工作。
- Scheduler(调度器):负责接收引擎发来的请求,并将其放入队列中,等待引擎请求时提供。它会根据优先级对请求进行排序,并负责去重。
- Downloader(下载器):负责下载网页内容。它接收引擎发来的请求,向目标网站发送 HTTP/HTTPS 请求,并将返回的响应(网页内容)发送给引擎。
- Spiders(爬虫):这是你编写爬虫逻辑的地方。它负责解析下载器返回的响应,提取所需数据,并生成新的请求(比如页面中的其他链接)。
- Item Pipelines(项目管道):负责处理爬虫提取到的数据。你可以将数据清洗、验证、持久化(保存到数据库、文件等)等操作放在这里。
- Downloader Middlewares(下载器中间件):介于引擎和下载器之间,可以在请求发送前和响应接收后进行处理。比如设置代理、User-Agent、处理 cookies 等。
- Spider Middlewares(爬虫中间件):介于引擎和爬虫之间,可以在请求发送给爬虫前和爬虫处理响应后进行处理。
Scrapy 的执行流程(单机版)
理解了核心组件,Scrapy 的执行流程就变得清晰起来:
- 启动爬虫:当你运行
scrapy crawl your_spider_name
命令时,Scrapy 引擎会启动。 - 生成初始请求:引擎会向你定义的
Spider
发送一个信号,告知其开始爬取。Spider
会生成初始的Request
对象(通常是爬取入口 URL),并将其发送给引擎。 - 请求入队:引擎接收到
Request
后,会将其转发给Scheduler
。Scheduler
将请求添加到队列中,并进行去重。 - 请求出队,发送下载:引擎从
Scheduler
请求下一个可用的Request
。Scheduler
将Request
返回给引擎。引擎再将Request
转发给Downloader Middlewares
进行预处理(比如添加代理)。 - 下载页面:经过
Downloader Middlewares
处理的Request
会被发送给Downloader
。Downloader
向目标网站发送 HTTP/HTTPS 请求,获取网页内容。 - 响应处理:
Downloader
接收到网站响应后,会将其封装成Response
对象,并先经过Downloader Middlewares
进行后处理。 - 响应解析:经过
Downloader Middlewares
处理的Response
对象被发送回引擎。引擎再将其转发给Spider Middlewares
进行预处理,最后交给Spider
进行解析。 - 数据提取与新请求生成:
Spider
接收到Response
后,会根据你编写的解析规则(通常使用 XPath 或 CSS 选择器)提取所需数据,并封装成Item
对象。同时,它可能会从当前页面中提取新的链接,并生成新的Request
对象。 - 数据入管道,新请求入队:
Spider
将Item
和新的Request
都发送给引擎。引擎将Item
转发给Item Pipelines
进行后续处理(清洗、存储等)。新的Request
则再次发送给Scheduler
,重复步骤 3-8。 - 爬虫结束:当
Scheduler
中没有待处理的Request
,并且所有Item Pipelines
都处理完毕时,Scrapy 引擎会停止,爬虫任务完成。
动手实践:一个简单的 Scrapy 爬虫
为了让你更好地理解,我们来创建一个简单的 Scrapy 爬虫,抓取某个网站的标题和链接。
安装 Scrapy:
pip install scrapy
创建 Scrapy 项目:
scrapy startproject myproject cd myproject
创建 Spider:
scrapy genspider example example.com
这会在
myproject/myproject/spiders/
目录下生成example.py
文件。编辑
example.py
:import scrapy class ExampleSpider(scrapy.Spider): name = "example" allowed_domains = ["example.com"] # 限制爬取范围 start_urls = ["http://example.com"] # 初始请求的URL def parse(self, response): # 提取页面标题 title = response.css('h1::text').get() # 提取所有链接 links = response.css('a::attr(href)').getall() yield { 'title': title, 'links': links, } # 如果需要继续爬取其他页面,可以生成新的请求 # for link in links: # yield response.follow(link, callback=self.parse)
运行爬虫:
scrapy crawl example -o output.json
这会将抓取到的数据保存到
output.json
文件中。
Scrapy-Redis 篇:分布式爬虫的实现
单机 Scrapy 爬虫在处理大量数据时可能会遇到性能瓶颈,比如内存占用过高、请求频率受限等。这时,Scrapy-Redis 就派上用场了!它能将 Scrapy 爬虫的请求队列和去重指纹存储到 Redis 数据库中,从而实现多个爬虫实例共享队列、协同工作,达到分布式爬取的效果。
为什么需要 Scrapy-Redis?
- 分布式爬取:多台机器可以同时运行爬虫实例,共享同一个 Redis 队列,提高爬取效率。
- 断点续爬:请求队列和去重指纹存储在 Redis 中,即使爬虫意外中断,重启后也能从上次中断的地方继续爬取,避免重复抓取和数据丢失。
- 共享 Request 队列:所有爬虫实例从同一个 Redis 队列中获取待爬取的 URL,避免重复爬取。
- 共享去重指纹:Redis 存储了已爬取 URL 的指纹,确保不会重复爬取相同页面。
Scrapy-Redis 的核心原理
Scrapy-Redis 的核心在于将 Scrapy 的 Scheduler 和 DupeFilter(去重过滤器)替换掉,转而使用 Redis 来存储和管理请求队列以及去重指纹。
当使用 Scrapy-Redis 时,Scrapy 的执行流程会发生如下变化:
- 启动爬虫(Scrapy-Redis 版):当你运行 Scrapy-Redis 爬虫时,它会连接到 Redis 数据库。
- 初始请求入 Redis:
Spider
生成的初始Request
对象不再直接发送给 Scrapy 内置的Scheduler
,而是被发送到 Redis 队列中。 - 请求从 Redis 出队:Scrapy-Redis 的
Scheduler
会从 Redis 队列中获取待爬取的Request
。 - 去重在 Redis:在将
Request
放入队列前或从队列取出后,Scrapy-Redis 的DupeFilter
会检查 Redis 中是否已经存在该请求的指纹,从而实现去重。 - 后续流程不变:一旦
Request
从 Redis 队列中取出并经过去重,后续的下载、响应处理、数据提取等流程与单机 Scrapy 基本一致。 - 新请求入 Redis:
Spider
解析页面后生成的新Request
也同样发送到 Redis 队列中。
动手实践:构建一个 Scrapy-Redis 分布式爬虫
接下来,我们来改造上面的 Scrapy 爬虫,让它具备分布式能力。
安装 Scrapy-Redis 和 Redis:
pip install scrapy-redis redis
你还需要在本地或服务器上安装并运行 Redis 服务。具体安装方法请参考 Redis 官方文档。
修改
settings.py
文件:
在你的 Scrapy 项目的settings.py
文件中,添加或修改以下配置:# 启用 Scrapy-Redis 的调度器和去重过滤器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 设置调度器是否持久化。如果为 True,Scrapy 停止后,Redis 中的请求队列和指纹不会被清除。 SCHEDULER_PERSIST = True # 设置 Redis 连接信息 REDIS_HOST = 'localhost' # 你的 Redis 服务器地址 REDIS_PORT = 6379 # 你的 Redis 服务器端口 REDIS_DB = 0 # Redis 数据库编号 # 可以设置 Scrapy-Redis 的队列类型,默认是 LIFO(栈),也可以改为 FIFO(队列) # SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue" # 队列 # SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderStack" # 栈 # SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue" # 优先级队列 # 如果需要爬虫启动时清除 Redis 中之前的请求,可以设置为 False # Dont cleanup redis queues, allows to start where was disconnected. # SCHEDULER_PERSIST = True
修改
example.py
文件:
让你的Spider
继承scrapy_redis.spiders.RedisSpider
。
注意: 继承RedisSpider
后,你的start_urls
不再生效,你需要通过 Redis 来提交初始 URL。import scrapy from scrapy_redis.spiders import RedisSpider class ExampleSpider(RedisSpider): # 继承 RedisSpider name = "example_redis" # 更改爬虫名称,避免与之前的冲突 allowed_domains = ["example.com"] # start_urls 不再需要,初始 URL 将从 Redis 中获取 # start_urls = ["http://example.com"] # Redis 键名,用于存储待爬取的 URL redis_key = 'example_redis:start_urls' def parse(self, response): title = response.css('h1::text').get() links = response.css('a::attr(href)').getall() yield { 'title': title, 'links': links, } for link in links: # 使用 response.follow 生成新的请求,这些请求会自动发送到 Redis yield response.follow(link, callback=self.parse)
启动 Redis:
确保你的 Redis 服务正在运行。提交初始 URL 到 Redis:
在运行爬虫之前,你需要手动将初始 URL 提交到 Redis 中。你可以使用 Redis 客户端工具(如redis-cli
)或者 Python 代码来完成。使用
redis-cli
:redis-cli LPUSH example_redis:start_urls http://example.com
这里的
example_redis:start_urls
就是你在Spider
中定义的redis_key
。LPUSH
命令将 URL 添加到 Redis 列表的左侧。运行分布式爬虫:
scrapy crawl example_redis
现在,你的爬虫实例会从 Redis 中获取 URL 进行爬取。你可以启动多个终端,运行相同的命令,它们会共享同一个 Redis 队列,实现分布式爬取。
分布式部署的关键步骤
- 准备多台机器:你需要多台服务器或虚拟机来部署你的爬虫实例。
- 安装 Redis:在一台独立的服务器上部署 Redis 服务,确保所有爬虫实例都能访问到它。
- 部署代码:将你的 Scrapy-Redis 项目代码部署到每台爬虫机器上。
- 启动爬虫:在每台机器上运行
scrapy crawl your_redis_spider_name
命令。
总结与展望
通过本篇博客,相信你已经对 Scrapy 和 Scrapy-Redis 的执行流程有了清晰的理解。
- Scrapy 提供了一个高效且模块化的框架,让你能够专注于数据提取和业务逻辑。
- Scrapy-Redis 则在此基础上,通过利用 Redis 的特性,实现了分布式爬虫的强大功能,包括断点续爬、共享队列、去重等,大大提升了大规模数据抓取的效率和稳定性。