5.1 Scrapy 扩展与中间件 5.1 Scrapy 扩展与中间件:深度定制爬虫行为 Scrapy 作为一个强大的爬虫框架,其灵活性很大程度上得益于其可扩展的架构。扩展(Extensions)和中间件(Middlewares)是 Scrapy 中两种重要的机制,它们允许开发者在引擎的不同阶段插入自定义逻辑,从而实现各种高级功能,例如: 请求重试和代理管理: 自动处理失败的请求,并轮换代理 IP,提高爬取成功率。 数据缓存: 缓存已爬取的数据,减少重复请求,提高效率。 User-Agent 轮换: 避免被网站识别为爬虫。 监控和统计: 收集爬虫运行时的各种指标,用于性能分析和问题诊断。 自定义下载器: 使用不同的下载器处理特定类型的请求,例如支持 JavaScript 渲染的请求。
Scrapy 作为一个强大的爬虫框架,其灵活性很大程度上得益于其可扩展的架构。扩展(Extensions)和中间件(Middlewares)是 Scrapy 中两种重要的机制,它们允许开发者在引擎的不同阶段插入自定义逻辑,从而实现各种高级功能,例如:
请求重试和代理管理: 自动处理失败的请求,并轮换代理 IP,提高爬取成功率。
数据缓存: 缓存已爬取的数据,减少重复请求,提高效率。
User-Agent 轮换: 避免被网站识别为爬虫。
监控和统计: 收集爬虫运行时的各种指标,用于性能分析和问题诊断。
自定义下载器: 使用不同的下载器处理特定类型的请求,例如支持 JavaScript 渲染的请求。
数据清洗和验证: 在数据进入 Pipeline 之前进行清洗和验证,保证数据的质量。
下面我们将深入探讨 Scrapy 扩展和中间件,并通过代码示例展示如何使用它们。
中间件是 Scrapy 的核心组件之一,它像一层层的“过滤器”一样,拦截并处理引擎和各个组件之间传递的数据。Scrapy 中间件主要分为三种类型:
Spider Middlewares(Spider 中间件): 处理 Spider 输入和输出的中间件。
Downloader Middlewares(下载器中间件): 处理请求(Request)和响应(Response)的中间件。
Scheduler Middlewares(调度器中间件): 处理请求调度过程的中间件 (不常用)。
Spider 中间件位于引擎和 Spider 之间,主要用于以下目的:
修改 Spider 发出的 Request 对象。
处理 Spider 返回的 Item 对象。
处理 Spider 抛出的异常。
Spider 中间件需要实现以下方法(可选):
process_spider_input(response, spider):在 Response 被传递给 Spider 之前调用。
process_spider_output(response, result, spider):在 Spider 处理 Response 后调用,result 是 Spider 返回的 Item 或 Request 对象的迭代器。
process_spider_exception(response, exception, spider):当 Spider 抛出异常时调用。
process_start_requests(start_requests, spider): 处理 start_requests ,必须返回一个可迭代对象。
示例:自定义 Spider 中间件
# myproject/middlewares.py class CustomSpiderMiddleware: def process_spider_input(self, response, spider): # 在 Response 进入 Spider 之前打印 URL print(f"Spider Input: {response.url}") return None # 返回 None 表示 Scrapy 继续处理 Response def process_spider_output(self, response, result, spider): # 处理 Spider 的输出结果 for item in result: # 可以对 Item 进行修改或过滤 yield item def process_spider_exception(self, response, exception, spider): # 处理 Spider 抛出的异常 print(f"Spider Exception: {exception}") return None # 返回 None 表示 Scrapy 继续处理异常
要在 Scrapy 中启用这个中间件,需要在 settings.py 中配置:
# settings.py SPIDER_MIDDLEWARES = { 'myproject.middlewares.CustomSpiderMiddleware': 543, }
数字 543 表示中间件的优先级,数字越小,优先级越高。
下载器中间件位于引擎和下载器之间,负责处理 Request 和 Response 对象。它们可以用于实现以下功能:
修改 Request 的 Headers、Proxy 等属性。
处理下载失败的请求,例如重试。
自定义下载器,例如使用 Selenium 处理 JavaScript 渲染的页面。
下载器中间件需要实现以下方法(可选):
process_request(request, spider):在 Request 发送给下载器之前调用。
process_response(request, response, spider):在下载器收到 Response 之后,但在传递给 Spider 之前调用。
process_exception(request, exception, spider):当下载器抛出异常时调用。
示例:自定义下载器中间件,实现 User-Agent 轮换
# myproject/middlewares.py import random class RandomUserAgentMiddleware: def __init__(self, user_agent_list): self.user_agent_list = user_agent_list @classmethod def from_crawler(cls, crawler): # 从 settings.py 中获取配置 return cls(crawler.settings.getlist('USER_AGENT_LIST')) def process_request(self, request, spider): # 随机选择一个 User-Agent ua = random.choice(self.user_agent_list) request.headers['User-Agent'] = ua
在 settings.py 中配置 User-Agent 列表和启用中间件:
# settings.py USER_AGENT_LIST = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:53.0) Gecko/20100101 Firefox/53.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', ] DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.RandomUserAgentMiddleware': 543, }
示例:自定义下载器中间件,实现代理 IP 轮换
# myproject/middlewares.py import random class ProxyMiddleware: def __init__(self, proxy_list): self.proxy_list = proxy_list @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('PROXY_LIST')) def process_request(self, request, spider): proxy = random.choice(self.proxy_list) request.meta['proxy'] = proxy def process_exception(self, request, exception, spider): # 处理请求异常,例如代理连接失败 print(f"Proxy Exception: {exception}") # 可以尝试更换代理,或者将请求重新加入队列 return request # 返回 request 表示重新调度请求
在 settings.py 中配置代理 IP 列表和启用中间件:
# settings.py PROXY_LIST = [ 'http://10.10.1.10:3128', 'http://10.10.1.11:1080', 'http://user:password@10.10.1.12:8080', # 需要用户名和密码的代理 ] DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.ProxyMiddleware': 750, }
调度器中间件主要用于处理请求调度过程,可以用于实现以下功能:
过滤重复的请求。
修改请求的优先级。
自定义调度策略。
调度器中间件需要实现以下方法(可选):
process_request(request, spider):在 Request 加入调度队列之前调用。
process_response(request, response, spider):在 Response 被 Spider 处理之后调用 (不常用)。
示例:自定义调度器中间件,过滤重复请求
# myproject/middlewares.py import hashlib class DupeFilterMiddleware: def __init__(self): self.seen_urls = set() def process_request(self, request, spider): # 使用 URL 的哈希值作为指纹 fp = hashlib.sha1(request.url.encode('utf-8')).hexdigest() if fp in self.seen_urls: # 如果 URL 已经爬取过,则忽略该请求 return None # 返回 None 表示 Scrapy 忽略该请求 else: self.seen_urls.add(fp) return None
在 settings.py 中启用中间件:
# settings.py SCHEDULER_MIDDLEWARES = { 'myproject.middlewares.DupeFilterMiddleware': 1, }
扩展是 Scrapy 中用于扩展核心功能的机制。与中间件不同,扩展通常用于执行一些全局性的任务,例如:
监控爬虫的运行状态。
发送邮件通知。
将爬取的数据导出到不同的格式。
控制爬虫的暂停和恢复。
扩展可以监听 Scrapy 的各种信号,并在特定事件发生时执行相应的操作。
要创建一个 Scrapy 扩展,需要定义一个类,并实现以下方法(可选):
from_crawler(cls, crawler):这是一个类方法,用于从 Crawler 对象中获取配置。
spider_opened(spider):当 Spider 启动时调用。
spider_closed(spider, reason):当 Spider 关闭时调用。
item_scraped(item, spider): 当 Item 被抓取时调用。
request_scheduled(request, spider): 当 Request 被调度时调用。
示例:自定义扩展,监控爬虫运行状态
# myproject/extensions.py from scrapy import signals class StatsMonitorExtension: def __init__(self, stats): self.stats = stats @classmethod def from_crawler(cls, crawler): # 从 Crawler 对象中获取 StatsCollector 对象 ext = cls(crawler.stats) # 连接信号 crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened) crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed) return ext def spider_opened(self, spider): print(f"Spider opened: {spider.name}") def spider_closed(self, spider, reason): print(f"Spider closed: {spider.name}, reason: {reason}") # 打印统计信息 print(self.stats.get_stats())
要在 settings.py 中启用这个扩展:
# settings.py EXTENSIONS = { 'myproject.extensions.StatsMonitorExtension': 500, }
Scrapy 使用信号机制来实现扩展的功能。信号是一种事件通知机制,当特定的事件发生时,Scrapy 会发出一个信号,扩展可以监听这些信号,并在信号发出时执行相应的操作。
常用的 Scrapy 信号包括:
spider_opened:当 Spider 启动时发出。
spider_closed:当 Spider 关闭时发出。
item_scraped:当 Item 被抓取时发出。
request_scheduled:当 Request 被调度时发出。
response_received:当 Response 被接收时发出。
item_dropped:当 Item 被丢弃时发出。
| 特性 | 中间件 | 扩展 |
|---|---|---|
| 作用范围 | 处理 Request 和 Response 对象,以及 Spider 的输入和输出 | 执行全局性的任务,例如监控、通知等 |
| 触发时机 | 在引擎和组件之间传递数据时触发 | 在特定事件发生时触发,例如 Spider 启动或关闭时 |
| 实现方式 | 实现特定的方法,例如 process_request、process_response |
实现特定的方法,并监听 Scrapy 的信号 |
| 配置方式 | 在 settings.py 中配置 DOWNLOADER_MIDDLEWARES 或 SPIDER_MIDDLEWARES |
在 settings.py 中配置 EXTENSIONS |
下面是一个综合应用的示例,展示如何使用中间件和扩展来实现一个更完整的爬虫功能:
需求:
使用 User-Agent 轮换,避免被网站识别为爬虫。
使用代理 IP 轮换,提高爬取成功率。
监控爬虫的运行状态,并在爬虫关闭时发送邮件通知。
代码实现:
middlewares.py (User-Agent 轮换和代理 IP 轮换)
extensions.py (监控和邮件通知)
settings.py (配置)
# middlewares.py import random class RandomUserAgentMiddleware: def __init__(self, user_agent_list): self.user_agent_list = user_agent_list @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('USER_AGENT_LIST')) def process_request(self, request, spider): ua = random.choice(self.user_agent_list) request.headers['User-Agent'] = ua class ProxyMiddleware: def __init__(self, proxy_list): self.proxy_list = proxy_list @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('PROXY_LIST')) def process_request(self, request, spider): proxy = random.choice(self.proxy_list) request.meta['proxy'] = proxy def process_exception(self, request, exception, spider): print(f"Proxy Exception: {exception}") return request
# extensions.py from scrapy import signals from scrapy.mail import MailSender class EmailNotificationExtension: def __init__(self, stats, mailer, recipients): self.stats = stats self.mailer = mailer self.recipients = recipients @classmethod def from_crawler(cls, crawler): ext = cls(crawler.stats, MailSender.from_settings(crawler.settings), crawler.settings.getlist('EMAIL_RECIPIENTS')) crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed) return ext def spider_closed(self, spider, reason): stats = self.stats.get_stats() body = f"Spider {spider.name} closed with reason: {reason}\n\nStats:\n{stats}" self.mailer.send(to=self.recipients, subject=f"Scrapy Spider {spider.name} Closed", body=body)
# settings.py USER_AGENT_LIST = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:53.0) Gecko/20100101 Firefox/53.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', ] PROXY_LIST = [ 'http://10.10.1.10:3128', 'http://10.10.1.11:1080', ] DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.RandomUserAgentMiddleware': 543, 'myproject.middlewares.ProxyMiddleware': 750, } EXTENSIONS = { 'myproject.extensions.EmailNotificationExtension': 500, } # 配置邮件服务器 MAIL_FROM = 'your_email@example.com' MAIL_HOST = 'smtp.example.com' MAIL_PORT = 587 MAIL_USER = 'your_email@example.com' MAIL_PASS = 'your_password' MAIL_TLS = True EMAIL_RECIPIENTS = ['recipient1@example.com', 'recipient2@example.com']
以下是一个 Mermaid 图表,展示了 Scrapy 中间件和扩展的交互关系:
图表解释:
Engine: Scrapy 引擎,负责协调各个组件。
Downloader Middlewares: 下载器中间件,处理 Request 和 Response 对象。
Downloader: 下载器,负责下载网页内容。
Spider Middlewares: Spider 中间件,处理 Spider 的输入和输出。
Spider: 爬虫,负责解析网页内容,提取数据。
Scheduler Middlewares: 调度器中间件,处理请求调度过程。
Scheduler: 调度器,负责管理请求队列。
Extensions: 扩展,监听 Scrapy 的信号,执行全局性的任务。
Signals: Scrapy 信号,用于通知扩展特定事件的发生。
Scrapy 的扩展和中间件机制为开发者提供了强大的自定义能力。通过合理地使用它们,可以实现各种高级功能,例如请求重试、代理管理、数据缓存、监控和统计等,从而构建更加健壮和高效的爬虫系统。掌握扩展和中间件的使用是成为 Scrapy 高级开发者的关键一步。