协程

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

支持的可调用对象

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

使用基于 Deferred 的 API

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

这些 API 具有基于协程的实现和基于 Deferred 的实现

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

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

例如:自定义调度器需要定义一个 open() 方法,该方法可以返回一个 Deferred 对象。您可以编写一个直接处理并返回 Deferred 的方法,也可以编写一个协程并使用 deferred_f_from_coro_f() 将其转换为返回 Deferred 的函数。

通用用法

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):
        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):
        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 反应器时想要 await Deferreds,您需要包装它们

异步代码的常见用例包括

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

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

  • 延迟爬虫初始化直到某个外部事件发生(在 spider_opened 处理器中);

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

内联请求

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

from scrapy import Spider, Request


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

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

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

import asyncio

from scrapy import Spider, Request


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"),
        ]
        tasks = []
        for r in additional_requests:
            task = self.crawler.engine.download_async(r)
            tasks.append(task)
        responses = await asyncio.gather(*tasks)
        yield {
            "h1": response.css("h1::text").get(),
            "price": responses[0].css(".price::text").get(),
            "price2": responses[1].css(".color::text").get(),
        }