【爬虫入门第10讲】Scrapy 框架深度解析(一):五大组件与中间件

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 可以轻松扩展为分布式。核心原理:

  1. 共享请求队列:所有爬虫实例从同一个 Redis List 中 pop 请求。
  2. 共享去重集合:使用 Redis Set 存储请求指纹,避免重复下载。
  3. 共享 Item 管道:每个实例独立处理 Item,但最终写入同一数据库。

部署架构

复制代码
[爬虫实例1] ←→ [Redis] ←→ [爬虫实例2]
↓                      ↓
[数据库]              [数据库]

注意事项

  • 确保所有实例的 Spider 代码一致。
  • 使用 scrapy crawl 启动多个进程,或通过 scrapyd 管理。
  • 监控 Redis 内存,避免请求队列无限增长。

五、性能优化建议

  1. 调整并发参数 :根据目标网站和服务器性能,适当增加 CONCURRENT_REQUESTSCONCURRENT_REQUESTS_PER_DOMAIN
  2. 启用缓存 :使用 scrapy.extensions.httpcache.FilesystemCacheStorage 缓存已下载页面,减少重复请求。
  3. 使用异步管道 :Pipeline 中的数据库写入操作应使用异步驱动(如 motor for MongoDB)或放入线程池。
  4. 合理设置下载延迟DOWNLOAD_DELAY 可以控制请求间隔,避免被反爬。
  5. 禁用不必要的中间件 :默认启用的中间件如 HttpProxyMiddlewareCookiesMiddleware 如果不需要,可以关闭以节省资源。
  6. 使用 Item Loaders :通过 ItemLoader 统一处理输入清洗和输出转换,提高代码复用性。

六、总结

Scrapy 的五大组件和两大中间件构成了一个高度模块化、可扩展的爬虫框架。Spider 负责业务逻辑,Engine 协调流程,Scheduler 管理请求队列,Downloader 执行网络请求,Pipeline 处理数据;中间件则提供了灵活的拦截点。理解这些组件的职责和交互方式,是高效使用 Scrapy 的基础。

无论是简单的单机爬虫,还是复杂的分布式系统,Scrapy 都能提供稳定、高效的解决方案。希望本文能帮助你从架构层面深入理解 Scrapy,并在实际项目中游刃有余。

相关推荐
IPdodo_1 天前
Python 接入代理IP:爬虫网络优化实战
爬虫·python·网络代理·代理ip·ipdodo
kisloy1 天前
【爬虫入门第9讲】Python爬虫浏览器自动化
爬虫·python·自动化
独行侠影a1 天前
Selenium 爬虫反爬实战:Python 模拟浏览器点击动态页面采集
爬虫·python·selenium
q567315232 天前
企业级 HTTP 代理采购选型:技术评估清单 15 项
开发语言·网络·爬虫·网络协议·http·隧道ip·代理ip
kisloy2 天前
【爬虫入门第8讲】Python爬虫反爬入门
开发语言·爬虫·python
深蓝电商API2 天前
Selenium 已经过时了吗?
爬虫
晴天162 天前
AI应用开发(智能体-全栈开发工程师 岗位分析)-Day04
人工智能·中间件
kisloy3 天前
【爬虫入门第2讲】浏览器开发者工具完全指南
人工智能·爬虫·tensorflow
fuquxiaoguang3 天前
MCP协议移交Linux基金会:AI智能体中间件标准定型,国产厂商迎“Kubernetes时刻”
linux·人工智能·中间件