协程

版本 2.0 中的新功能。

Scrapy 对 协程语法 提供了 部分支持

支持的可调用对象

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

一般用法

在 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 时对 Deferred 进行 await 操作,则需要 包装它们

异步代码的常见用例包括

  • 从网站、数据库和其他服务请求数据(在回调函数、管道和中间件中);

  • 将数据存储在数据库中(在管道和中间件中);

  • 将爬虫初始化延迟到某个外部事件(在 spider_opened 处理程序中);

  • 调用异步 Scrapy 方法,如 ExecutionEngine.download()(请参阅 屏幕截图管道示例)。

内联请求

下面的爬虫演示了如何在爬虫回调函数中发送请求并等待其响应

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(),
        }

混合同步和异步爬虫中间件

版本 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 方法

注意

当使用仅定义同步 process_spider_output 方法的第三方爬虫中间件时,请考虑 使其成为通用中间件,方法是通过 子类化

通用爬虫中间件

版本 2.7 中的新功能。

为了允许编写一个爬虫中间件,该中间件支持在 Scrapy 2.7 及更高版本中异步执行其 process_spider_output 方法(避免 异步到同步的转换),同时保持对较旧 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 的后续版本中,此功能将被移除,并且所有 Spider 中间件都将需要将其 process_spider_output 方法定义为异步生成器。