Scrapy 框架深度解析:五大组件与中间件全攻略
Scrapy 是 Python 生态中最成熟、最强大的异步爬虫框架之一,由 Zyte(原 Scrapinghub)开发维护。它基于 Twisted 事件循环引擎,天然支持异步并发,能够高效处理大规模网页抓取任务。本文将从架构设计、核心组件、中间件机制、分布式扩展、性能调优等维度,带你全面掌握 Scrapy 的精髓。
一、Scrapy 整体架构概览
Scrapy 的架构设计遵循"管道-过滤器"模式,数据流在五大组件之间单向流动,中间件则提供拦截和修改数据流的能力。这种设计使得每个组件职责单一、可替换、可扩展,开发者只需关注业务逻辑(Spider 和 Pipeline),其余工作由框架自动完成。
数据流核心路径
Spider(生成初始请求)
→ Engine(接收请求)
→ Scheduler(排队)
→ Engine(取出请求)
→ Downloader Middleware(预处理请求)
→ Downloader(发送 HTTP 请求)
→ Downloader Middleware(后处理响应)
→ Engine(接收响应)
→ Spider Middleware(预处理响应)
→ Spider(解析响应,生成 Item 或新请求)
→ Spider Middleware(后处理 Item/请求)
→ Engine(分发 Item 给 Pipeline,新请求给 Scheduler)
二、五大组件详解
1. Spider(爬虫)------ 业务逻辑的容器
Spider 是开发者编写爬取规则的地方,它定义了如何解析网页、提取数据、生成后续请求。Scrapy 提供了多种内置 Spider 基类:
- scrapy.Spider :最基础的爬虫,需定义
start_requests()或start_urls,以及parse()回调。 - CrawlSpider:基于规则(Rule)自动跟踪链接,适合整站爬取。
- XMLFeedSpider / CSVFeedSpider:专门处理 XML/CSV 格式的响应。
- SitemapSpider:从站点地图中提取 URL。
示例:一个简单的新闻爬虫
python
import scrapy
class NewsSpider(scrapy.Spider):
name = 'news'
start_urls = ['https://example.com/news']
def parse(self, response):
for article in response.css('div.article'):
yield {
'title': article.css('h2::text').get(),
'url': article.css('a::attr(href)').get(),
'date': article.css('span.date::text').get()
}
# 翻页
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield scrapy.Request(url=response.urljoin(next_page), callback=self.parse)
关键点:
- 每个请求必须指定
callback,默认是parse方法。 - 使用
yield返回 Item(字典或 Item 对象)或新的 Request。 - 可以通过
meta参数在请求之间传递数据。
2. Engine(引擎)------ 系统的中枢神经
Engine 是 Scrapy 的核心调度器,它协调所有组件的交互。主要职责包括:
- 接收 Spider 的初始请求,交给 Scheduler 排队。
- 从 Scheduler 取出下一个待下载的请求,交给 Downloader 执行。
- 接收 Downloader 返回的 Response,交给 Spider 处理。
- 将 Spider 产生的 Item 发送给 Pipeline,将新请求送回 Scheduler。
Engine 内部维护了多个队列和状态机,确保数据流有序、高效。开发者通常不需要直接操作 Engine,但可以通过信号(Signals)和扩展(Extensions)在特定事件发生时执行自定义逻辑。
3. Scheduler(调度器)------ 请求的排队与去重
Scheduler 负责管理待下载的请求队列,并决定下一个请求的优先级。默认实现基于内存的优先队列(heapq),支持请求去重(使用 scrapy.dupefilters.RFPDupeFilter,基于请求指纹)。
分布式扩展 :通过 scrapy-redis 库,可以将 Scheduler 替换为基于 Redis 的分布式调度器。所有爬虫实例共享同一个 Redis 请求队列和去重集合,从而实现多机协同爬取。
python
# settings.py 配置分布式
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_URL = 'redis://localhost:6379'
调度策略:
- 默认按优先级(Priority)和入队时间排序。
- 支持延迟请求(
download_delay)和并发控制(CONCURRENT_REQUESTS)。 - 可自定义调度器类,实现更复杂的策略(如按域名限速、按请求类型分流)。
4. Downloader(下载器)------ 网络请求的执行者
Downloader 基于 Twisted 的异步 HTTP 客户端,支持 HTTP/1.1、HTTPS、代理、Cookie、重定向、超时等。它内部维护一个连接池,复用 TCP 连接以提高效率。
关键配置 (在 settings.py 中):
python
# 并发请求数(默认16)
CONCURRENT_REQUESTS = 32
# 每个域名的并发数(默认8)
CONCURRENT_REQUESTS_PER_DOMAIN = 16
# 每个IP的并发数(默认0,无限制)
CONCURRENT_REQUESTS_PER_IP = 8
# 下载超时(秒)
DOWNLOAD_TIMEOUT = 15
# 启用Cookies(默认True)
COOKIES_ENABLED = True
# 重试次数
RETRY_TIMES = 2
# 启用代理
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
}
异步并发原理:Twisted 使用 Reactor 事件循环,通过非阻塞 I/O 和回调机制实现高并发。Scrapy 将每个请求封装为一个 Deferred 对象,当响应到达时触发回调,从而避免线程切换开销。
5. Item Pipeline(数据管道)------ 数据的清洗与存储
Pipeline 负责处理 Spider 提取的 Item,每个 Pipeline 是一个实现了 process_item 方法的类。多个 Pipeline 按优先级顺序执行,每个 Pipeline 可以决定是否继续处理(返回 Item)或丢弃(抛出 DropItem 异常)。
常见用途:
- 数据清洗:去除空白、转换格式、验证字段。
- 去重:使用数据库或集合检查重复。
- 存储:写入 MySQL、MongoDB、CSV、JSON 等。
- 发送通知:邮件、消息队列。
示例:MongoDB 存储 Pipeline
python
import pymongo
class MongoPipeline:
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db['articles'].insert_one(dict(item))
return item
配置 Pipeline 优先级(数字越小越先执行):
python
ITEM_PIPELINES = {
'myproject.pipelines.MongoPipeline': 300,
'myproject.pipelines.DuplicatesPipeline': 200,
}
三、两大中间件机制
中间件是 Scrapy 架构的"钩子",允许在数据流经过时插入自定义逻辑。它们分为下载中间件和爬虫中间件,分别位于 Engine 与 Downloader、Engine 与 Spider 之间。
1. Downloader Middleware(下载中间件)
下载中间件可以处理请求和响应,常用于:
- 随机 User-Agent:避免被网站封禁。
- 代理 IP 轮换:使用付费代理或自建代理池。
- 处理重定向和重试:自动处理 3xx 状态码,或对 5xx 错误进行重试。
- 修改请求头:添加 Referer、Authorization 等。
- 缓存响应:减少重复请求。
示例:随机 User-Agent 中间件
python
import random
from scrapy import signals
class RandomUserAgentMiddleware:
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...',
# 更多 UA
]
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(self.user_agents)
return None # 继续处理
def process_response(self, request, response, spider):
return response # 直接返回
def process_exception(self, request, exception, spider):
# 处理异常,如重试
return None
配置中间件:
python
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.RandomUserAgentMiddleware': 400,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, # 禁用默认
}
2. Spider Middleware(爬虫中间件)
爬虫中间件可以处理 Spider 的输入(Response)和输出(Request 或 Item)。常见用途:
- 过滤响应:根据状态码或内容类型决定是否交给 Spider。
- 修改 Item:在进入 Pipeline 前进行统一处理。
- 动态调整请求优先级:根据解析结果改变后续请求的优先级。
- 记录爬取进度:统计已处理的 Item 数量。
示例:过滤非 HTML 响应
python
class FilterNonHtmlMiddleware:
def process_spider_input(self, response, spider):
if 'text/html' not in response.headers.get('Content-Type', b'').decode():
raise scrapy.exceptions.IgnoreRequest("Not HTML")
return None
def process_spider_output(self, response, result, spider):
for item in result:
yield item
def process_spider_exception(self, response, exception, spider):
pass
四、分布式爬虫实现
Scrapy 本身是单机框架,但通过 scrapy-redis 可以轻松扩展为分布式。核心原理:
- 共享请求队列:所有爬虫实例从同一个 Redis List 中 pop 请求。
- 共享去重集合:使用 Redis Set 存储请求指纹,避免重复下载。
- 共享 Item 管道:每个实例独立处理 Item,但最终写入同一数据库。
部署架构:
[爬虫实例1] ←→ [Redis] ←→ [爬虫实例2]
↓ ↓
[数据库] [数据库]
注意事项:
- 确保所有实例的 Spider 代码一致。
- 使用
scrapy crawl启动多个进程,或通过scrapyd管理。 - 监控 Redis 内存,避免请求队列无限增长。
五、性能优化建议
- 调整并发参数 :根据目标网站和服务器性能,适当增加
CONCURRENT_REQUESTS和CONCURRENT_REQUESTS_PER_DOMAIN。 - 启用缓存 :使用
scrapy.extensions.httpcache.FilesystemCacheStorage缓存已下载页面,减少重复请求。 - 使用异步管道 :Pipeline 中的数据库写入操作应使用异步驱动(如
motorfor MongoDB)或放入线程池。 - 合理设置下载延迟 :
DOWNLOAD_DELAY可以控制请求间隔,避免被反爬。 - 禁用不必要的中间件 :默认启用的中间件如
HttpProxyMiddleware、CookiesMiddleware如果不需要,可以关闭以节省资源。 - 使用 Item Loaders :通过
ItemLoader统一处理输入清洗和输出转换,提高代码复用性。
六、总结
Scrapy 的五大组件和两大中间件构成了一个高度模块化、可扩展的爬虫框架。Spider 负责业务逻辑,Engine 协调流程,Scheduler 管理请求队列,Downloader 执行网络请求,Pipeline 处理数据;中间件则提供了灵活的拦截点。理解这些组件的职责和交互方式,是高效使用 Scrapy 的基础。
无论是简单的单机爬虫,还是复杂的分布式系统,Scrapy 都能提供稳定、高效的解决方案。希望本文能帮助你从架构层面深入理解 Scrapy,并在实际项目中游刃有余。