资讯动态

CallLaterOnce —— Scrapy 调度循环的去重开关

发布时间:2026/8/25 13:48:44 来源:尧图企业网站定制
本文基于Scrapy 2.17.0 + Twisted 26.4.0。这是 《Scrapy 2.17 源码解析:ExecutionEngine 引擎调度循环详解》 的配套拓展篇——上一篇里那个只有 30 行、却决定了整个引擎调度效率的小类,值得单独拆一次。前言在引擎源码里,你会看到这样一行反复出现:self._slot.nextcall.schedule()它出现在:crawl()把请求塞进调度器之后_download()的finally块里每个请求 Deferred 链的末尾_start_request_processing()的 while 循环里5 秒一次的心跳里一次下载完成,可能连续触发三四次schedule()。并发 16 的时候,一轮下来就是几十次调用。如果每次都真的往事件循环里排一个回调,引擎会被自己压垮。CallLaterOnce就是拦在这中间的那道闸。一、完整源码scrapy/utils/reactor.py,一共 40 行:classCallLaterOnce(Generic[_T]):"""Schedule a function to be called in the next reactor loop, but only if it hasn't been already scheduled since the last time it ran. """def__init__(self,func:Callable[_P,_T],*a:_P.args,**kw:_P.kwargs):self._func:Callable[_P,_T]=func self._a:tuple[Any,...]=a self._kw:dict[str,Any]=kw self._call:CallLaterResult|None=Noneself._deferreds:list[Deferred[None]]=[]defschedule(self,delay:float=0)-None:fromscrapy.utils.asyncioimportcall_laterifself._callisNone:self._call=call_later(delay,self)defcancel(self)-None:ifself._call:self._call.cancel()def__call__(self)-_T:fromscrapy.utils.asyncioimportcall_later self._call=Noneresult=self._func(*self._a,**self._kw)fordinself._deferreds:call_later(0,d.callback,None)self._deferreds.clear()returnresultasyncdefwait(self)-None:fromscrapy.utils.deferimportmaybe_deferred_to_future d:Deferred[None]=Deferred()self._deferreds.append(d)awaitmaybe_deferred_to_future(d)短,但每一行都有理由。下面逐个拆。二、_call指针:幂等的全部秘密整个类的核心就是self._call这一个字段。它有两个状态:_call的值含义None当前没有排期,下一次schedule()会真正排一个回调CallLaterResult已经排期了,后续schedule()全部无效配合两个方法形成闭环:defschedule(self,delay:float=0)-None:ifself._callisNone:

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价