Scrapy 靠 Twisted 的 reactor 在一个线程里同时处理几百个请求。2.19 里默认 reactor 已经是 asyncio 的,还有完全不用 reactor 的模式。这篇用同一个 spider 跑三种方式,看日志、线程、阻塞回调和 async 回调的真实差别。
[asyncio reactor(默认)] 设置 (无),退出码 0
[scrapy.crawler] DEBUG: Using AsyncCrawlerProcess
[scrapy.utils.log] DEBUG: Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor
[scrapy.utils.log] DEBUG: Using asyncio event loop: asyncio.unix_events._UnixSelectorEventLoop
[SelectReactor] 设置 {'TWISTED_REACTOR': 'twisted.internet.selectreactor.SelectReactor'},退出码 0
[scrapy.crawler] DEBUG: Using CrawlerProcess
[scrapy.utils.log] DEBUG: Using reactor: twisted.internet.selectreactor.SelectReactor
[无 reactor] 设置 {'TWISTED_REACTOR_ENABLED': 'False'},退出码 0
[scrapy.crawler] DEBUG: Using AsyncCrawlerProcess
[scrapy.crawler] DEBUG: Not using a Twisted reactor
[scrapy.core.downloader.handlers._base_streaming] WARNING: AiohttpDownloadHandler is experimental and is not recommended for production use.(http 与 https 各打印一次)
默认值:TWISTED_REACTOR = twisted.internet.asyncioreactor.AsyncioSelectorReactor True None 10 (依次:TWISTED_REACTOR、TWISTED_REACTOR_ENABLED、ASYNCIO_EVENT_LOOP、REACTOR_THREADPOOL_MAXSIZE)2.19 里 TWISTED_REACTOR 默认就是 AsyncioSelectorReactor,用的进程类是 AsyncCrawlerProcess;设成别的 reactor 则回到 CrawlerProcess;TWISTED_REACTOR_ENABLED=False 不装 reactor,直接用 asyncio 事件循环,下载由实验性的 AiohttpDownloadHandler 完成。
方式 总耗时 s 服务器并发峰值 回调线程 线程数(最大) 成功响应 asyncio reactor(默认) 0.62 100 MainThread 2 101 SelectReactor 0.67 100 MainThread 2 101 无 reactor 0.59 100 MainThread 1 101
网络 I/O 是非阻塞的,100 个各等 0.5 s 的请求同时在路上,总耗时 0.6 s 左右;所有回调都在 MainThread 里运行。
情形 总耗时 s 理想值 回调什么都不做 0.34 0.3+≈0(状态码 200 的响应 21 个) 回调里 time.sleep(0.1) 2.39 0.3+20×0.1=2.3(状态码 200 的响应 21 个) 回调里 time.sleep(0.1),并发改成 1 8.21 20×(0.3+0.1)=8(状态码 200 的响应 21 个) async def 回调里 await asyncio.sleep(0.1) 0.43 0.3+0.1(状态码 200 的响应 21 个)
回调、中间件、pipeline 与网络 I/O 共用一个线程,同步的 time.sleep 或重计算会拖住所有请求。需要等待时用 async def 和 await。
[asyncio reactor(默认)] 总耗时 0.45 s;spider_exceptions/count=0;response_received_count=21
[SelectReactor] 总耗时 0.35 s;spider_exceptions/count=20;response_received_count=21
异常:RuntimeError: no running event loop
[无 reactor] 总耗时 0.43 s;spider_exceptions/count=0;response_received_count=21默认 reactor 和无 reactor 模式都在 asyncio 事件循环上,await asyncio.sleep 可以用;SelectReactor 下没有运行中的 asyncio 事件循环,会抛 RuntimeError: no running event loop。
默认事件循环:asyncio.unix_events._UnixSelectorEventLoop;3 次的 elapsed_time_seconds [0.302, 0.299, 0.335],中位数 0.302 s;301 个响应 uvloop:uvloop.Loop;3 次的 elapsed_time_seconds [0.275, 0.277, 0.271],中位数 0.275 s;301 个响应
ASYNCIO_EVENT_LOOP 可以指定事件循环类,例如 uvloop.Loop。在本机零延迟的小实验里 uvloop 略快,但差距有限。