2.9 Signals (信号)


文档摘要

2.9 Signals (信号) Scrapy 核心组件详解:Signals (信号) 在 Scrapy 框架中,Signals (信号) 是一种事件通知机制,允许开发者在特定的事件发生时执行自定义的操作。 它们提供了一种解耦 Scrapy 组件的方式,使得我们可以在不修改核心代码的情况下,扩展和定制 Scrapy 的行为。 信号机制基于观察者模式,允许不同的组件监听特定事件,并在事件发生时执行相应的处理函数。 信号机制的核心概念 信号 (Signals): 代表 Scrapy 内部发生的特定事件,例如 spider 启动、item 被 scraped、请求完成等。Scrapy 定义了许多内置信号,开发者也可以自定义信号。 信号发送者 (Signal Sender): 负责发出信号的对象。

2.9 Signals (信号)

Scrapy 核心组件详解:Signals (信号)

在 Scrapy 框架中,Signals (信号) 是一种事件通知机制,允许开发者在特定的事件发生时执行自定义的操作。 它们提供了一种解耦 Scrapy 组件的方式,使得我们可以在不修改核心代码的情况下,扩展和定制 Scrapy 的行为。 信号机制基于观察者模式,允许不同的组件监听特定事件,并在事件发生时执行相应的处理函数。

1. 信号机制的核心概念

  • 信号 (Signals): 代表 Scrapy 内部发生的特定事件,例如 spider 启动、item 被 scraped、请求完成等。Scrapy 定义了许多内置信号,开发者也可以自定义信号。

  • 信号发送者 (Signal Sender): 负责发出信号的对象。 通常是 Scrapy 框架内部的组件,例如 engine, downloader, spider 等。

  • 信号接收者 (Signal Receiver): 监听特定信号并执行相应处理函数 (handler) 的对象。 接收者可以是 Scrapy 的任何组件,甚至是自定义的扩展。

  • 信号处理函数 (Signal Handler): 也称为回调函数,当信号被触发时,接收者执行的函数。 处理函数可以接收信号发送者传递的参数,并根据需要执行相应的操作。

2. Scrapy 内置信号

Scrapy 提供了丰富的内置信号,涵盖了爬虫生命周期的各个阶段。 以下是一些常用的内置信号:

  • spider_opened: 在 spider 启动时发送。

  • spider_closed: 在 spider 关闭时发送。

  • item_scraped: 在 item 被成功 scraped 时发送。

  • item_dropped: 在 item 被 pipeline 丢弃时发送。

  • request_scheduled: 在请求被调度器调度时发送。

  • request_dropped: 在请求被调度器丢弃时发送。

  • response_received: 在 response 被 downloader 下载后发送。

  • spider_error: 在 spider 发生错误时发送。

  • engine_started: 在 Scrapy 引擎启动时发送。

  • engine_stopped: 在 Scrapy 引擎停止时发送。

3. 使用 Signals 的优势

  • 解耦性: 信号机制允许不同的组件独立地工作,而无需直接依赖彼此。 这使得 Scrapy 的架构更加灵活和可维护。

  • 可扩展性: 通过监听信号,开发者可以在不修改 Scrapy 核心代码的情况下,添加自定义的功能和行为。

  • 可定制性: 信号机制允许开发者在 Scrapy 的各个阶段插入自定义的逻辑,从而定制爬虫的行为。

  • 事件驱动: 信号机制使得 Scrapy 能够响应内部发生的事件,并根据事件执行相应的操作。

4. 如何使用 Signals

使用 Scrapy 的信号机制需要以下几个步骤:

  1. 定义信号处理函数 (Signal Handler): 创建一个函数,用于处理特定的信号。该函数可以接收信号发送者传递的参数。

  2. 连接信号和处理函数: 使用 signals.connect() 方法将信号处理函数与特定的信号连接起来。

  3. 指定信号发送者 (可选): 可以指定信号的发送者,以便只监听来自特定对象的信号。

4.1 代码实践

下面是一个简单的例子,演示如何使用 spider_openedspider_closed 信号:

import scrapy from scrapy import signals from scrapy.crawler import CrawlerProcess class MySpider(scrapy.Spider): name = "myspider" start_urls = ["http://example.com"] def parse(self, response): yield {"title": response.xpath("//title/text()").get()} def spider_opened(spider): print(f"Spider {spider.name} opened!") def spider_closed(spider, reason): print(f"Spider {spider.name} closed with reason: {reason}") def item_scraped(item, spider): print(f"Item scraped: {item} from spider: {spider.name}") return item process = CrawlerProcess() spider = MySpider() # 连接信号和处理函数 process.signals.connect(spider_opened, signal=signals.spider_opened) process.signals.connect(spider_closed, signal=signals.spider_closed) process.signals.connect(item_scraped, signal=signals.item_scraped) process.crawl(spider) process.start()

代码解释:

  1. 定义信号处理函数:

    • spider_opened(spider): 在 spider 启动时被调用,接收 spider 对象作为参数。

    • spider_closed(spider, reason): 在 spider 关闭时被调用,接收 spider 对象和关闭原因作为参数。

    • item_scraped(item, spider): 在 item 被成功 scraped 时被调用,接收 item 对象和 spider 对象作为参数。

  2. 连接信号和处理函数:

    • process.signals.connect(spider_opened, signal=signals.spider_opened): 将 spider_opened 函数与 spider_opened 信号连接起来。

    • process.signals.connect(spider_closed, signal=signals.spider_closed): 将 spider_closed 函数与 spider_closed 信号连接起来。

    • process.signals.connect(item_scraped, signal=signals.item_scraped): 将 item_scraped 函数与 item_scraped 信号连接起来。

  3. 运行爬虫:

    • process.crawl(spider): 启动爬虫。

    • process.start(): 启动 Scrapy 引擎。

运行结果:

当运行这个爬虫时,你会在控制台中看到以下输出:

Spider myspider opened! Item scraped: {'title': 'Example Domain'} from spider: myspider Spider myspider closed with reason: finished

这个例子展示了如何使用 spider_openedspider_closed 信号来在 spider 启动和关闭时执行自定义的操作。 同样,item_scraped 信号则是在成功爬取到 Item 时被触发。

4.2 在扩展中使用 Signals

更常见的是,信号被用于创建 Scrapy 扩展。 下面是一个使用信号来记录请求和响应的示例:

from scrapy import signals from scrapy.exceptions import NotConfigured import logging class RequestResponseLogger: def __init__(self, crawler): self.crawler = crawler self.logger = logging.getLogger(__name__) @classmethod def from_crawler(cls, crawler): # 获取 settings 中定义的配置 if not crawler.settings.getbool('REQUEST_RESPONSE_LOGGER_ENABLED'): raise NotConfigured ext = cls(crawler) # 连接信号 crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened) crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed) crawler.signals.connect(ext.request_scheduled, signal=signals.request_scheduled) crawler.signals.connect(ext.response_received, signal=signals.response_received) return ext def spider_opened(self, spider): self.logger.info(f"Spider {spider.name} started.") def spider_closed(self, spider, reason): self.logger.info(f"Spider {spider.name} closed with reason: {reason}") def request_scheduled(self, request, spider): self.logger.debug(f"Request scheduled: {request.url} from spider: {spider.name}") def response_received(self, response, request, spider): self.logger.debug(f"Response received: {response.url} for request: {request.url} from spider: {spider.name}")

代码解释:

  1. 定义扩展类:

    • RequestResponseLogger: 定义一个扩展类,用于记录请求和响应。
  2. from_crawler 方法:

    • @classmethod from_crawler(cls, crawler): 这是一个类方法,用于创建扩展实例。 Scrapy 使用这个方法来加载扩展。

    • if not crawler.settings.getbool('REQUEST_RESPONSE_LOGGER_ENABLED'): raise NotConfigured: 根据 settings 中的配置决定是否启用扩展。

    • crawler.signals.connect(...): 将扩展的方法与相应的信号连接起来。

  3. 信号处理函数:

    • spider_opened(self, spider): 在 spider 启动时被调用,记录 spider 启动信息。

    • spider_closed(self, spider, reason): 在 spider 关闭时被调用,记录 spider 关闭信息。

    • request_scheduled(self, request, spider): 在请求被调度时被调用,记录请求的 URL。

    • response_received(self, response, request, spider): 在接收到响应时被调用,记录响应的 URL 和对应的请求 URL。

  4. 配置 settings.py:

    REQUEST_RESPONSE_LOGGER_ENABLED = True SPIDER_MIDDLEWARES = { 'your_project.extensions.RequestResponseLogger': 543, # 替换 your_project 为你的项目名称 }

运行结果:

启用此扩展后,您将在日志中看到有关已调度请求和收到的响应的信息。

5. 自定义 Signals

除了使用 Scrapy 提供的内置信号外,我们还可以自定义信号。 自定义信号允许我们在特定的业务逻辑中触发事件,并执行相应的处理函数.

5.1 如何定义自定义信号

自定义信号需要使用 scrapy.signals.Signal 类。 例如,我们可以定义一个 item_validated 信号,在 item 被验证后发送:

from scrapy import signals item_validated = signals.Signal()

5.2 如何发送自定义信号

要发送自定义信号,需要使用 send_catch_log 方法。 例如,在 item 被验证后,我们可以发送 item_validated 信号:

from scrapy import signals from scrapy.utils.misc import load_object from scrapy.exceptions import NotConfigured item_validated = signals.Signal() class ValidationMiddleware: def __init__(self, crawler): self.crawler = crawler @classmethod def from_crawler(cls, crawler): mw = cls(crawler) crawler.signals.connect(mw.spider_opened, signal=signals.spider_opened) return mw def spider_opened(self, spider): self.validator = load_object(spider.settings.get('ITEM_VALIDATOR'))(spider=spider) def process_item(self, item, spider): try: self.validator.validate(item) except Exception as e: spider.logger.error(f"Item validation failed: {e}") raise DropItem(e) else: # 发送自定义信号 spider.crawler.signals.send_catch_log(item_validated, item=item, spider=spider) return item

代码解释:

  1. 定义自定义信号:

    • item_validated = signals.Signal(): 定义一个名为 item_validated 的自定义信号。
  2. process_item 方法中发送信号:

    • spider.crawler.signals.send_catch_log(item_validated, item=item, spider=spider): 在 item 被成功验证后,发送 item_validated 信号。

    • item=item, spider=spider: 将 item 对象和 spider 对象作为参数传递给信号处理函数。

5.3 如何接收自定义信号

接收自定义信号的方式与接收内置信号相同。 例如,我们可以创建一个扩展来监听 item_validated 信号:

from scrapy import signals from scrapy.exceptions import NotConfigured import logging class ItemValidatedLogger: def __init__(self, crawler): self.crawler = crawler self.logger = logging.getLogger(__name__) @classmethod def from_crawler(cls, crawler): if not crawler.settings.getbool('ITEM_VALIDATED_LOGGER_ENABLED'): raise NotConfigured ext = cls(crawler) crawler.signals.connect(ext.item_validated, signal=item_validated) return ext def item_validated(self, item, spider): self.logger.info(f"Item validated: {item} from spider: {spider.name}")

代码解释:

  1. 定义扩展类:

    • ItemValidatedLogger: 定义一个扩展类,用于记录 item 被验证的信息。
  2. from_crawler 方法:

    • crawler.signals.connect(ext.item_validated, signal=item_validated): 将扩展的 item_validated 方法与 item_validated 信号连接起来。
  3. 信号处理函数:

    • item_validated(self, item, spider): 在 item 被成功验证后被调用,记录 item 的信息。

6. 信号机制的流程图

可以使用 mermaid 绘制一个简单的流程图,说明信号机制的工作原理:

流程图解释:

  1. 事件发生: Scrapy 内部发生一个事件,例如 spider 启动、item 被 scraped 等。

  2. 信号发送者: 信号发送者检测到事件发生,并发出相应的信号。

  3. 信号: 信号代表发生的事件。

  4. 信号接收者: 信号接收者监听特定的信号。

  5. 信号处理函数: 当信号被触发时,信号接收者执行相应的信号处理函数。

  6. 执行操作: 信号处理函数执行自定义的操作,例如记录日志、修改 item 等。

7. 总结

Scrapy 的 Signals 机制是一种强大的工具,用于扩展和定制爬虫的行为。 通过理解和使用 Signals,开发者可以更好地控制爬虫的各个阶段,并实现更复杂的功能。 无论是使用内置信号还是自定义信号,都可以使 Scrapy 爬虫更加灵活、可维护和可扩展。 掌握 Signals 是成为 Scrapy 高级开发者的关键一步。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U