协程¶
版本 2.0 中的新功能。
支持的可调用对象¶
以下可调用对象可以使用 async def
定义为协程,因此可以使用协程语法(例如 await
、async for
、async with
)
Request
回调函数。如果您使用任何自定义或第三方 爬虫中间件,请参阅 混合同步和异步爬虫中间件。
版本 2.7 中的变化: 异步回调函数的输出现在异步处理,而不是先收集所有输出。
数据项管道 的
process_item()
方法。下载器中间件 的
process_request()
、process_response()
和process_exception()
方法。爬虫中间件 的
process_spider_output()
方法。它必须定义为一个 异步生成器。输入
result
参数是一个 异步可迭代对象。另请参阅 混合同步和异步爬虫中间件 和 通用爬虫中间件。
版本 2.7 中的新功能。
一般用法¶
在 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 方法。
通用爬虫中间件¶
版本 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
方法定义为异步生成器。