协程¶
版本2.0新增。
支持的可调用对象¶
以下可调用对象可以使用async def定义为协程,因此可以使用协程语法(例如await、async for、async with):
Request回调函数。如果您正在使用任何自定义或第三方的spider middleware,请参阅混合同步和异步spider middlewares。
在版本2.7中更改:异步回调的输出现在异步处理,而不是先收集所有输出。
process_item()方法是 item pipelines 的一部分。下载器中间件的
process_request()、process_response()、 和process_exception()方法。的
process_spider_output()方法属于 spider middlewares。它必须被定义为一个异步生成器。输入
result参数是一个异步可迭代对象。另请参阅 混合同步和异步爬虫中间件 和 通用爬虫中间件。
新版本2.7中新增。
一般用法¶
在Scrapy中有几个使用协程的案例。
为以前的Scrapy版本编写的代码(如下载器中间件和信号处理程序)会返回Deferreds,可以重写为更短更简洁的代码:
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
协程可用于调用异步代码。这包括其他协程、返回Deferreds的函数以及返回可等待对象的函数,例如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,
你需要wrap them。
异步代码的常见用例包括:
从网站、数据库和其他服务请求数据(在回调、管道和中间件中);
将数据存储在数据库中(在管道和中间件中);
延迟蜘蛛初始化,直到某些外部事件(在
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 参数传递给第一个 spider middleware 的 process_spider_output() 方法,该 spider middleware 来自 active spider middlewares 列表。然后,该 process_spider_output 方法的输出传递给下一个 spider middleware 的 process_spider_output 方法,依此类推,直到所有 active spider middleware 都处理完毕。
Scrapy 支持在调用链中混合使用 coroutine methods 和同步方法。
然而,如果任何一个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的后续版本中移除,所有爬虫中间件都将被期望将其process_spider_output方法定义为异步生成器。