Coroutines (协程)
版本 2.0 新增。
Scrapy 支持协程语法(即 async def)。
Supported callables (支持的可调用对象)
以下可调用对象可以使用 async def 定义为协程,因此可以使用协程语法(例如 await、async for、async 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()
MailSendersend()
以下用户提供的方法可以返回 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() 方法,而不是重复处理代码。