bigywater.tech
Scrapy · 40

async def 回调:协程怎么接入 Twisted

回调写成 async def 之后,Scrapy 怎么接收它们的返回值、会不会并行、能不能 await Twisted 的 Deferred、能不能直接用 aiohttp?这篇把八种回调写法和几个常见的坑一次测完。

0.33 vs 6.25 s
20 个等待 0.3 s 的回调:async + await 与同步 time.sleep 的总耗时
RuntimeError
在 asyncio reactor 下直接 await Deferred 的结果
136 / 1138 ms
异步生成器两个 item 到达 pipeline 的时间(中间 await 1 s)

返回值

实测 · 本机执行输出
  def,return 一个 dict              item=[1];cb2=-;spider_exceptions=0
  def,return 列表 [dict, Request]   item=[2];cb2=被调用;spider_exceptions=0
  def 生成器,yield                   item=[3];cb2=-;spider_exceptions=0
  async def,return 一个 dict        item=[4];cb2=-;spider_exceptions=0
  async def,return 列表             item=[5, 6];cb2=-;spider_exceptions=0
  async def,await 之后 return       item=[7];cb2=-;spider_exceptions=0
  async def 异步生成器,yield           item=[8, 9];cb2=-;spider_exceptions=0
  async def,return None           item=[];cb2=-;spider_exceptions=0

回调可以是普通函数、生成器、协程或异步生成器;可以 return 一个对象、一个列表,也可以逐个 yield。引擎只区分每个产出物是 Request 还是 item。

流式

实测 · 本机执行输出
      136 ms  cb 开始
      136 ms  pipeline {'k': 'first'}
     1138 ms  pipeline {'k': 'second'}

普通 Spider 里异步生成器边 yield 边处理;CrawlSpider 的规则回调会先收集完整个异步生成器。

并发

实测 · 本机执行输出
  async def + await asyncio.sleep(0.3)    总耗时 0.33 s
  def + time.sleep(0.3)                   总耗时 6.25 s

async 回调在同一个事件循环里并发;同步的阻塞调用会把所有回调串行化。

Deferred

实测 · 本机执行输出
  直接 await Deferred                 → RuntimeError: Task got bad yield: <Deferred at 0x…>
  await maybe_deferred_to_future(d) → deferred-result
  await deferred_to_future(d)       → deferred-result

asyncio reactor 下要用 maybe_deferred_to_future / deferred_to_future 才能 await Twisted 的 Deferred。

第三方异步库

实测 · 本机执行输出
  服务器收到 40 个请求(20 + 20 个 aiohttp 请求);item_scraped_count=20;总耗时 0.03 s

可以直接在回调里用 aiohttp 等基于 asyncio 的库,但它们的请求不经过 Scrapy 的下载器中间件、去重和限速。

错误处理

实测 · 本机执行输出
  500 响应(HttpError)→ async errback 产出的 item:[{'from_errback': 500}];stats spider_exceptions=0
  async 回调抛异常:spider_exceptions/ValueError=1;日志 ['[scrapy.core.scraper] ERROR: Spider error processing <GET http://127.0.0.1:9960/x> (referer: None)']

async 回调抛的异常和同步回调一样计入 spider_exceptions;async errback 同样可以 yield item。

分步演示

异步回调
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Scrapy 2.19.0 / Twisted 26.4.0 / lxml 6.1.3 / parsel 1.12.1(临时虚拟环境);所有请求只发给本机自己起的测试服务器(127.0.0.1,端口 9960 以上,不访问任何真实网站);服务器可以按实验需要制造延迟、重定向、压缩、断连等行为,并记录收到的原始请求;每个实验在临时目录里新建 Scrapy 项目或单文件 spider,命令输出来自真实的 scrapy 进程;计时用 time.perf_counter,数字每次运行会有小幅抖动。每个情形是一个单文件 spider,pipeline 里用 EV() 记录收到的 item;aiohttp 使用虚拟环境里的 aiohttp 3.14.5
    • 没有验证:无 reactor 模式(TWISTED_REACTOR_ENABLED=False)下同样的写法;SelectReactor 下 async def 回调的支持程度(上一批文章里只测过 asyncio.sleep 会失败);async 回调里 time.sleep 之外的阻塞调用(如 requests)的整体影响;CrawlSpider 收集异步生成器后的内存影响;回调里使用 Playwright/Selenium 等的细节
    京ICP备2026067502号-1京公网安备11010502063487号