asyncio

Scrapy 原生支持 asyncio。使用 scrapy startproject 创建的新项目默认启用 asyncio,你可以在任何 协程 中使用 asyncio 和基于 asyncio 的库。

本页的其余部分涵盖了高级主题。如果你正在启动一个新项目,则无需额外设置。

配置 asyncio 反应器

使用 scrapy startproject 生成的新项目默认配置了 asyncio 反应器。无需手动设置。

设置 TWISTED_REACTOR 控制 Scrapy 使用哪个 Twisted 反应器。其默认值为 'twisted.internet.asyncioreactor.AsyncioSelectorReactor',这会启用 asyncio 支持。

如果你正在使用 AsyncCrawlerRunnerCrawlerRunner,你还需要手动安装 AsyncioSelectorReactor 反应器。你可以使用 install_reactor() 来完成。

install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")

处理预安装的反应器

twisted.internet.reactor 和其他一些 Twisted 导入会作为副作用安装默认的 Twisted 反应器。一旦安装了 Twisted 反应器,就无法在运行时切换到不同的反应器。

如果你 配置了 asyncio Twisted 反应器,并且在运行时 Scrapy 抱怨已安装了不同的反应器,那么很可能是你的代码中存在一些此类导入。

你通常可以通过将那些有问题的模块级 Twisted 导入移动到它们被使用的方法或函数定义中来解决此问题。例如,如果你有类似以下的代码:

from twisted.internet import reactor


def my_function():
    reactor.callLater(...)

切换到类似以下的代码:

def my_function():
    from twisted.internet import reactor

    reactor.callLater(...)

或者,你可以在这些导入发生之前,尝试 手动安装 asyncio 反应器,使用 install_reactor()

集成 Deferred 代码与 asyncio 代码

协程函数可以通过将 Deferred 封装到 asyncio.Future 对象中来等待它们。Scrapy 为此提供了两个助手函数:

scrapy.utils.defer.deferred_to_future(d: Deferred[_T]) Future[_T][source]

返回一个封装了 dasyncio.Future 对象。

此函数需要已安装的 asyncio 反应器或正在运行的 asyncio 事件循环,详见 asyncio

在这种状态下,你不能在 定义为协程的 Scrapy 可调用对象 中等待 Deferred 对象,你只能等待 Future 对象。将 Deferred 对象封装到 Future 对象中可以让你等待它们。

class MySpider(Spider):
    ...

    async def parse(self, response):
        deferred = some_dfd_helper()
        result = await deferred_to_future(deferred)

2.14 版本中的变更: 如果在 Twisted asyncio 反应器安装之前调用此函数,它将不再安装 asyncio 循环。在这种情况下会引发 RuntimeError

scrapy.utils.defer.maybe_deferred_to_future(d: Deferred[_T]) Deferred[_T] | Future[_T][source]

d 作为可在 定义为协程的 Scrapy 可调用对象 中等待的对象返回。

在定义为协程的 Scrapy 可调用对象中你可以等待什么,取决于 TWISTED_REACTORTWISTED_REACTOR_ENABLED 的值。

如果你想编写使用 Deferred 对象但在上述两种状态下都能工作的代码,请在所有 Deferred 对象上使用此函数。

class MySpider(Spider):
    ...

    async def parse(self, response):
        deferred = some_dfd_helper()
        result = await maybe_deferred_to_future(deferred)

提示

如果你不需要支持除默认 AsyncioSelectorReactor 之外的反应器,可以使用 deferred_to_future(),否则应使用 maybe_deferred_to_future()

提示

如果你需要在旨在与不提供这些函数(低至 Scrapy 2.0;早期版本不支持 asyncio)的较低版本 Scrapy 兼容的代码中使用这些函数,你可以将这些函数的实现复制到自己的代码中。

协程和 Future 可以使用以下助手函数封装成 Deferred(例如,当 Scrapy API 需要向其传递 Deferred 时):

scrapy.utils.defer.deferred_from_coro(o: Awaitable[_T]) Deferred[_T][source]
scrapy.utils.defer.deferred_from_coro(o: _T2) _T2

将协程或其他可等待对象转换为 Deferred,如果它不是协程,则按原样返回该对象。

scrapy.utils.defer.deferred_f_from_coro_f(coro_f: Callable[_P, Awaitable[_T]]) Callable[_P, Deferred[_T]][source]

将协程函数转换为返回 Deferred 的函数。

协程函数将在调用包装器时被调用。包装器参数将传递给它。这对于回调链很有用,因为回调函数是以前一个回调结果调用的。

scrapy.utils.defer.ensure_awaitable(o: Awaitable[_T], _warn: str | None = None) Awaitable[_T][source]
scrapy.utils.defer.ensure_awaitable(o: _T, _warn: str | None = None) Awaitable[_T]

将任何值转换为可等待对象。

对于 Deferred 对象,使用 maybe_deferred_to_future() 将其封装成合适的对象。对于不同类型的可等待对象,按原样返回。对于任何其他值,返回一个以该值完成的协程。

2.14 版本中新增。

强制将 asyncio 作为一项要求

如果你正在编写需要 asyncio 才能工作的 组件,请使用 scrapy.utils.asyncio.is_asyncio_available()强制将其作为一项要求。例如:

from scrapy.utils.asyncio import is_asyncio_available


class MyComponent:
    def __init__(self):
        if not is_asyncio_available():
            raise ValueError(
                f"{MyComponent.__qualname__} requires the asyncio support. "
                f"Make sure you have configured the asyncio reactor in the "
                f"TWISTED_REACTOR setting. See the asyncio documentation "
                f"of Scrapy for more information."
            )
scrapy.utils.asyncio.is_asyncio_available() bool[source]

检查是否可以调用依赖于 asyncio 事件循环的 asyncio 代码。

2.14 版本中新增。

如果存在正在运行的 asyncio 事件循环,此函数返回 True。如果不存在这样的循环,则如果安装的 Twisted 反应器是 AsyncioSelectorReactor,则返回 True;如果安装了不同的反应器,则返回 False;如果未安装反应器,则引发 RuntimeError

不直接需要 Twisted 反应器的代码应使用此函数,而需要 AsyncioSelectorReactor 的代码应使用 is_asyncio_reactor_installed()

当此函数返回 True 时,Scrapy 已安装并使用了 asyncio 循环。可以调用需要它的函数,例如 asyncio.sleep(),并在 Scrapy 相关代码中等待 asyncio.Future 对象。

当此函数返回 False 时,已安装非 asyncio 的 Twisted 反应器。在这种情况下,无法在 Scrapy 相关代码中使用需要 asyncio 事件循环的 asyncio 功能或等待 asyncio.Future 对象,但可以等待 Deferred 对象。

注意

由于此函数使用 asyncio.get_running_loop(),因此只有在同一线程中并从在该循环内部运行的代码调用时,它才会检测到事件循环(如果 Scrapy 以受支持的方式运行,从爬虫和 Scrapy 组件等代码中调用它应该不成问题)。

2.15.0 版本中的变更: 此函数现在在存在正在运行的 asyncio 循环时也返回 True,即使没有安装 Twisted 反应器。

scrapy.utils.reactor.is_asyncio_reactor_installed() bool[source]

检查已安装的反应器是否为 AsyncioSelectorReactor

如果未安装反应器,则引发 RuntimeError

在未来的 Scrapy 版本中,当 Scrapy 支持在没有 Twisted 反应器的情况下运行时,此函数将不再适用于检查是否可以使用 asyncio 功能,因此不直接需要 Twisted 反应器的代码应改用 scrapy.utils.asyncio.is_asyncio_available() 而不是此函数。

2.13 版本中的变更: 在早期 Scrapy 版本中,如果未安装反应器,此函数会静默安装默认反应器。现在,它会引发异常以防止在这种情况下出现静默问题。

在不使用 Twisted 反应器的情况下使用 Scrapy

2.15.0 版本中新增。

警告

此功能目前是实验性的,可能不适合生产使用。

通过将 TWISTED_REACTOR_ENABLED 设置为 False,可以在不安装 Twisted 反应器的情况下使用 Scrapy。在此模式下,Scrapy 将直接使用 asyncio 事件循环,并且大多数 Scrapy 功能将以相同的方式工作。

这样做在某些用例中提供了以下几个好处:

  • Twisted 反应器一旦停止,就无法再次启动。这会阻止例如在同一进程中使用多个 AsyncCrawlerProcess 实例(当它们使用反应器时),但将 TWISTED_REACTOR_ENABLED=False 设置后,这变为可能。

  • 可能存在 AsyncioSelectorReactor 及相关 Twisted 代码施加的限制,例如在 Windows 上要求使用 SelectorEventLoop(参见 Windows 特定注意事项),如果未使用反应器则这些限制不适用。

  • AsyncioSelectorReactor 管理底层事件循环,虽然 AsyncCrawlerRunner 可以使用预先存在的反应器,而该反应器又可以使用预先存在的事件循环,但直接将 AsyncCrawlerRunner 与预先存在的循环一起使用会更简单。

  • 省略反应器机制可能会提高性能和可靠性。

局限性

由于某些 Scrapy 功能和组件需要反应器,因此在没有反应器的情况下它们无法工作并被禁用。在未来的 Scrapy 版本中可能会添加不需要反应器的替代方案。以下功能不可用:

请注意,导入 Twisted 模块以及创建和使用 Deferred 对象等操作并不需要反应器,因此使用 DeferredFailure 和其他一些 Twisted API 的代码不一定会停止工作。

其他区别

TWISTED_REACTOR_ENABLED 设置为 False 时,Scrapy 将更改一些其他设置的默认值:

因此,HttpxDownloadHandler 默认用于发出 HTTP(S) 请求。有关其与 HTTP11DownloadHandler 的差异和局限性,请参阅其文档。

此外,AsyncCrawlerProcess 将安装一个 元路径查找器,以防止 twisted.internet.reactor 被导入。

为现有代码添加支持

不直接使用 Twisted API 或依赖于 Twisted API 的代码,在没有反应器的情况下运行时不需要特殊支持。

以下是一些需要替换的 API 和模式示例:

Scrapy 为其中一些示例提供了统一的助手函数:

scrapy.utils.asyncio.call_later(delay: float, func: Callable[[Unpack[_Ts]], object], *args: Unpack[_Ts]) CallLaterResult[source]

在延迟后调度一个函数被调用。

这会使用 asyncio.loop.call_later()reactor.callLater(),具体取决于 asyncio 支持是否可用。

在 2.14.0 版本中添加。

scrapy.utils.asyncio.create_looping_call(func: ~collections.abc.Callable[[~_P], ~scrapy.utils.asyncio._T], *args: ~typing.~_P, **kwargs: ~typing.~_P) AsyncioLoopingCall | LoopingCall[source]

创建循环调用类的实例。

这会创建 AsyncioLoopingCallLoopingCall 的实例,具体取决于 asyncio 支持是否可用。

在 2.14.0 版本中添加。

class scrapy.utils.asyncio.AsyncioLoopingCall(func: ~collections.abc.Callable[[~_P], ~scrapy.utils.asyncio._T], *args: ~typing.~_P, **kwargs: ~typing.~_P)[source]

一个使用 asyncio 实现周期性调用的简单实现,与 LoopingCall 保持一定的 API 和行为兼容性。

该函数每 interval 秒调用一次,与上次调用的完成时间无关。如果函数在再次调用时仍在运行,则会跳过调用,直到函数完成。

该函数不得返回协程或 Deferred

在 2.14.0 版本中添加。

async scrapy.utils.asyncio.run_in_thread(func: ~collections.abc.Callable[[~_P], ~scrapy.utils.asyncio._T], *args: ~typing.~_P, **kwargs: ~typing.~_P) _T[source]

在线程中调用一个函数,并将其结果作为协程返回。

这会使用 asyncio.to_thread()twisted.internet.threads.deferToThread(),具体取决于 asyncio 支持是否可用。

2.15.0 版本中新增。

如果你的代码需要知道反应器是否可用,你可以检查 TWISTED_REACTOR_ENABLED 设置的值(你需要访问 Crawler 实例才能做到这一点),或者使用以下函数:

scrapy.utils.reactorless.is_reactorless() bool[source]

检查我们是否在无反应器模式下运行,即 TWISTED_REACTOR_ENABLED 设置为 False

由于此函数检查的是运行时状态而非设置本身,因此在反应器和/或 asyncio 事件循环初始化之前的很早阶段执行时,结果可能不准确。

注意

由于此函数使用 scrapy.utils.asyncio.is_asyncio_available(),因此它在检测正在运行的 asyncio 事件循环方面具有相同的局限性。

2.15.0 版本中新增。

通常,不直接或间接使用反应器的代码,在有 asyncio 反应器和没有反应器的情况下都可以未经修改地使用。这包括在 集成 Deferred 代码与 asyncio 代码 中描述的将 Deferred 转换为 Future 或反之的代码。

故障排除

ImportError: 在没有 Twisted 反应器的情况下运行时,禁止导入 twisted.internet.reactor [...]: Scrapy 配置为在没有反应器的情况下运行,但某些代码导入了 twisted.internet.reactor,这很可能是因为该代码需要使用反应器。你需要停止使用此代码或将 TWISTED_REACTOR_ENABLED 重新设置为 True。反应器也可能并非真正需要,但由于 处理预安装的反应器 中描述的问题而安装,在这种情况下,修复有问题的导入就足够了。

RuntimeError: TWISTED_REACTOR_ENABLED 为 False 但已安装 Twisted 反应器: Scrapy 配置为在没有反应器的情况下运行,但在 Scrapy 代码执行之前已经安装了反应器。如果你尝试通过 每个爬虫设置 设置 TWISTED_REACTOR_ENABLED,目前不支持。

RuntimeError: 我们期望安装 Twisted 反应器但它未安装: Scrapy 配置为运行带有反应器但不安装反应器,但在 Scrapy 代码执行之前未安装反应器。如果你尝试通过 每个爬虫设置 设置 TWISTED_REACTOR_ENABLED,目前不支持。

RuntimeError: <class> 不支持 TWISTED_REACTOR_ENABLED=False: 列出的类不能与设置为 FalseTWISTED_REACTOR_ENABLED 一起使用。在 上面的文档 或受影响类的文档中可能有替代方案。

Windows 特定注意事项

Windows 上的 asyncio 实现可以使用两种事件循环实现:ProactorEventLoop(默认)和 SelectorEventLoop。然而,只有 SelectorEventLoop 适用于 Twisted。

当你更改 TWISTED_REACTOR 设置或调用 install_reactor() 时,Scrapy 会自动将事件循环类更改为 SelectorEventLoop

注意

你使用的其他库可能需要 ProactorEventLoop,例如因为它支持子进程(playwright 就是这种情况),因此你无法在 Windows 上将它们与 Scrapy 一起使用(但你可以在 WSL 或原生 Linux 上使用它们)。

注意

当不使用反应器时,此问题不适用,详见 在不使用 Twisted 反应器的情况下使用 Scrapy

使用自定义 asyncio 循环

你还可以将自定义 asyncio 事件循环与 asyncio 反应器一起使用。将 ASYNCIO_EVENT_LOOP 设置为所需事件循环类的导入路径,以使用它而不是默认的 asyncio 事件循环。

切换到非 asyncio 反应器

如果由于某种原因你的代码无法与 asyncio 反应器一起使用,你可以通过将 TWISTED_REACTOR 设置为其导入路径(例如 'twisted.internet.epollreactor.EPollReactor')或设置为 None 来使用不同的反应器,这将使用你平台的默认反应器。如果你正在使用 AsyncCrawlerRunnerAsyncCrawlerProcess,你还需要切换到它们基于 Deferred 的对应项:分别为 CrawlerRunnerCrawlerProcess