协程
支持的可调用对象
以下可调用对象可以使用 async def 定义为协程,因此可以使用协程语法(例如 await、async for、async with)
爬虫的
start()方法,其 必须 定义为 异步生成器。2.13 版本中新增。
Request回调。项目管道 的
process_item()方法。下载器中间件 的
process_request()、process_response()和process_exception()方法。爬虫中间件 的
process_spider_output()方法,其 必须 定义为 异步生成器,通用爬虫中间件 除外。爬虫中间件 的
process_start()方法,其 必须 定义为 异步生成器。2.13 版本中新增。
下载处理器 的方法。
2.14 版本中新增。
使用基于 Deferred 的 API
除了原生的协程 API,Scrapy 还提供一些 API,它们返回一个 Deferred 对象,或者接受一个返回 Deferred 对象的自定义函数。这些 API 也是异步的,但尚不支持原生的 async def 语法。我们计划在未来为这些 API 添加 async def 语法支持,或者在无法修改现有 API 的情况下用其他 API 替换它们。
这些 API 具有基于协程的实现和基于 Deferred 的实现
-
crawl_async()(基于协程)和crawl()(基于 Deferred):前者在基于 Deferred 的代码中可能不便使用,因此两者都可用;这在未来的 Scrapy 版本中可能会改变。
scrapy.crawler.AsyncCrawlerRunner及其子类scrapy.crawler.AsyncCrawlerProcess(基于协程)以及scrapy.crawler.CrawlerRunner及其子类scrapy.crawler.CrawlerProcess(基于 Deferred):前者不支持非默认反应器,因此在那些情况下应使用后者。
以下用户提供的方法可以返回 Deferred 对象(也可以返回协程的方法已在 支持的可调用对象 中列出)
自定义下载器实现(参见
DOWNLOADER)fetch()
自定义调度器实现(参见
SCHEDULER)自定义去重过滤器(参见
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()
在大多数情况下,您可以在使用协程的代码中使用这些 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(),
}