Lzh on GitHub

版本 2.0 新增。

Scrapy 支持协程语法(即 async def)。

Supported callables (支持的可调用对象)

以下可调用对象可以使用 async def 定义为协程,因此可以使用协程语法(例如 awaitasync forasync with):

  • start() 爬虫方法,它必须定义为异步生成器。 版本 2.13 新增。
  • Request 回调。 如果您正在使用任何自定义或第三方爬虫中间件,请参阅同步和异步爬虫中间件的混合使用。 版本 2.7 更改:异步回调的输出现在是异步处理的,而不是先收集所有输出。
  • 项目管道process_item() 方法。
  • 下载器中间件process_request()process_response()process_exception() 方法。
  • 爬虫中间件process_spider_output() 方法。 如果定义为协程,它必须是异步生成器。输入 result 参数是异步可迭代对象。 另请参阅同步和异步爬虫中间件的混合使用通用爬虫中间件。 版本 2.7 新增。
  • 爬虫中间件process_start() 方法,它必须定义为异步生成器。 版本 2.13 新增。
  • 支持延迟的信号处理程序

Using Deferred-based APIs (使用基于 Deferred 的 API)

除了原生协程 API,Scrapy 还有一些 API 返回 Deferred 对象或接受返回 Deferred 对象的由用户提供的函数。这些 API 也是异步的,但尚不支持原生 async def 语法。将来我们计划向这些 API 添加 async def 语法支持,或者在可能更改现有 API 的情况下用其他 API 替换它们。

以下 Scrapy 方法返回 Deferred 对象(此列表不完整,因为它只包含我们认为可能对用户代码有用的方法):

  • scrapy.crawler.Crawler:
    • crawl()
    • stop()
  • scrapy.crawler.CrawlerRunner (也由 scrapy.crawler.CrawlerProcess 继承):
    • crawl()
    • stop()
    • join()
  • scrapy.core.engine.ExecutionEngine:
    • download()
  • scrapy.signalmanager.SignalManager:
    • send_catch_log_deferred()
  • MailSender
    • send()

以下用户提供的方法可以返回 Deferred 对象(也可以返回协程的方法列在支持的可调用对象中):

  • 自定义下载处理程序(参阅 DOWNLOAD_HANDLERS):
    • download_request()
    • close()
  • 自定义下载器实现(参阅 DOWNLOADER):
    • fetch()
  • 自定义调度器实现(参阅 SCHEDULER):
    • open()
    • close()
  • 自定义去重过滤器(参阅 DUPEFILTER_CLASS):
    • open()
    • close()
  • 自定义 Feed 存储(参阅 FEED_STORAGES):
    • store()
  • scrapy.pipelines.media.MediaPipeline 的子类:
    • media_to_download()
    • item_completed()
  • scrapy.pipelines.files.FilesPipeline 子类使用的自定义存储:
    • persist_file()
    • stat_file()

在大多数情况下,您可以通过将 Deferred 对象包装到 Future 对象中或反之,在其他使用协程的代码中使用这些 API。有关此内容的更多信息,请参阅集成 Deferred 代码和 asyncio 代码

例如:

  • ExecutionEngine.download() 方法返回一个 Deferred 对象,该对象在下载响应时触发。您可以在基于 Deferred 的代码中直接使用此对象,或者使用 maybe_deferred_to_future() 将其转换为 Future 对象。
  • 自定义下载处理程序需要定义一个返回 Deferred 对象的 download_request() 方法。您可以编写一个处理 Deferred 并直接返回它的方法,或者您可以编写一个协程并使用 deferred_f_from_coro_f() 将其转换为返回 Deferred 的函数。

General usage (一般用法)

Scrapy 中协程有几种用例。

在以前的 Scrapy 版本中编写时会返回 Deferred 的代码(例如下载器中间件和信号处理程序)可以重写得更短、更简洁:

from itemadapter import ItemAdapter

class DbPipeline:
    def _update_item(self, data, item):
        adapter = ItemAdapter(item)
        adapter["field"] = data
        return item

    def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        dfd = db.get_some_data(adapter["id"])
        dfd.addCallback(self._update_item, item)
        return dfd

变成:

from itemadapter import ItemAdapter

class DbPipeline:
    async def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        adapter["field"] = await db.get_some_data(adapter["id"])
        return item

协程可以用于调用异步代码。这包括其他协程、返回 Deferred 的函数以及返回可等待对象(例如 Future)的函数。这意味着您可以使用许多提供此类代码的有用 Python 库:

class MySpiderDeferred(Spider):
    # ...
    async def parse(self, response):
        additional_response = await treq.get("https://additional.url")
        additional_data = await treq.content(additional_response)
        # ... use response and additional_data to yield items and requests

class MySpiderAsyncio(Spider):
    # ...
    async def parse(self, response):
        async with aiohttp.ClientSession() as session:
            async with session.get("https://additional.url") as additional_response:
                additional_data = await additional_response.text()
        # ... use response and additional_data to yield items and requests

注意

许多使用协程的库(例如 aio-libs)需要 asyncio 循环,要使用它们,您需要在 Scrapy 中启用 asyncio 支持

注意

如果您在使用 asyncio reactor 时想要 await Deferred,则需要包装它们

异步代码的常见用例包括:

  • 从网站、数据库和其他服务请求数据(在 start()、回调、管道和中间件中);
  • 将数据存储在数据库中(在管道和中间件中);
  • 延迟爬虫初始化直到某个外部事件(在 spider_opened 处理程序中);
  • 调用异步 Scrapy 方法,例如 ExecutionEngine.download()(请参阅截图管道示例)。

Inline requests (内联请求)

下面的爬虫展示了如何从爬虫回调中发送请求并等待其响应:

from scrapy import Spider, Request
from scrapy.utils.defer import maybe_deferred_to_future

class SingleRequestSpider(Spider):
    name = "single"
    start_urls = ["https://example.org/product"]

    async def parse(self, response, **kwargs):
        additional_request = Request("https://example.org/price")
        deferred = self.crawler.engine.download(additional_request)
        additional_response = await maybe_deferred_to_future(deferred)
        yield {
            "h1": response.css("h1").get(),
            "price": additional_response.css("#price").get(),
        }

您还可以并行发送多个请求:

from scrapy import Spider, Request
from scrapy.utils.defer import maybe_deferred_to_future
from twisted.internet.defer import DeferredList

class MultipleRequestsSpider(Spider):
    name = "multiple"
    start_urls = ["https://example.com/product"]

    async def parse(self, response, **kwargs):
        additional_requests = [
            Request("https://example.com/price"),
            Request("https://example.com/color"),
        ]
        deferreds = []
        for r in additional_requests:
            deferred = self.crawler.engine.download(r)
            deferreds.append(deferred)
        responses = await maybe_deferred_to_future(DeferredList(deferreds))
        yield {
            "h1": response.css("h1::text").get(),
            "price": responses[0][1].css(".price::text").get(),
            "price2": responses[1][1].css(".color::text").get(),
        }

Mixing synchronous and asynchronous spider middlewares (混合同步和异步爬虫中间件)

版本 2.7 新增。

Request 回调的输出作为 result 参数传递给活动爬虫中间件列表中第一个爬虫中间件process_spider_output() 方法。然后,该 process_spider_output 方法的输出传递给下一个爬虫中间件的 process_spider_output 方法,依此类推,直到所有活动的爬虫中间件。

Scrapy 支持在此调用链中混合协程方法和同步方法。

然而,如果任何 process_spider_output 方法被定义为同步方法,并且之前的 Request 回调或 process_spider_output 方法是协程,那么 Scrapy 在异步到同步转换方面存在一些缺点,以便同步的 process_spider_output 方法将其 result 参数获取为同步可迭代对象:

  • 之前 Request 回调或 process_spider_output 方法的整个输出在此刻被等待。
  • 如果在等待之前 Request 回调或 process_spider_output 方法的输出时发生异常,则不会处理该输出的任何部分。 这与常规行为形成对比,在常规行为中,在异常发生之前生成的所有项目都会被处理。

支持异步到同步转换是为了向后兼容,但它们已被弃用,并将在 Scrapy 的未来版本中停止工作。

为了避免异步到同步转换,当将 Request 回调定义为协程方法或在使用其 process_spider_output 方法是异步生成器的爬虫中间件时,所有活动爬虫中间件的 process_spider_output 方法都必须定义为异步生成器或定义一个 process_spider_output_async 方法

For middleware users (对于中间件用户)

如果您有异步回调或使用仅异步爬虫中间件,您应该确保上述的异步到同步转换不会发生。为此,请确保您使用的所有爬虫中间件都支持异步爬虫输出。即使您的项目中没有异步回调并且不使用仅异步爬虫中间件,最好还是确保您使用的所有中间件都支持异步爬虫输出,这样将来就可以轻松开始使用异步回调。因此,当 Scrapy 检测到仅同步爬虫中间件时,它会记录一个警告。

如果您想更新您编写的中间件,请参阅下一节。如果您有第三方中间件尚未由其作者更新,您可以子类化它们以使其通用,并在您的项目中使用这些子类。

For middleware authors (对于中间件作者)

如果您有一个爬虫中间件定义了一个同步的 process_spider_output 方法,您应该更新它以支持异步爬虫输出,以实现更好的兼容性,即使您尚未将其与异步回调一起使用,特别是如果您发布此中间件供其他人使用。您有两个选择:

  • 使中间件异步化,通过将 process_spider_output 方法设置为异步生成器
  • 使中间件通用化,如下一节所述。

如果您的中间件不会在仅同步中间件的项目中使用(例如,因为它是内部中间件,并且您知道项目中的所有其他中间件都已更新),则选择第一个选项是安全的。否则,最好选择第二个选项。

Universal spider middlewares (通用爬虫中间件)

版本 2.7 新增。

为了允许编写一个爬虫中间件,该中间件支持其 process_spider_output 方法在 Scrapy 2.7 及更高版本中的异步执行(避免异步到同步转换),同时保持对旧 Scrapy 版本的支持,您可以将 process_spider_output 定义为同步方法,并使用替代名称定义该方法的异步生成器版本:process_spider_output_async

例如:

class UniversalSpiderMiddleware:
    def process_spider_output(self, response, result, spider):
        for r in result:
            # ... do something with r
            yield r

    async def process_spider_output_async(self, response, result, spider):
        async for r in result:
            # ... do something with r
            yield r

注意

这是一个临时措施,旨在允许在 Scrapy 2.7 及更高版本中编写无需异步到同步转换即可工作的代码,并且也适用于更早的 Scrapy 版本。

然而,在 Scrapy 的未来某个版本中,此功能将被弃用,最终在 Scrapy 的后续版本中,此功能将被移除,并且所有爬虫中间件都将期望将其 process_spider_output 方法定义为异步生成器。

自 2.13.0 起,Scrapy 提供了一个基类 BaseSpiderMiddleware,它实现了 process_spider_output()process_spider_output_async() 方法,因此您可以重写 get_processed_request() 和/或 get_processed_item() 方法,而不是重复处理代码。