twisted的task之cooperator和scrapy的parallel()函数
本文是关于下载结果返回后调用item处理的过程实现研究。
从scrapy的结果处理说起
def handle_spider_output(self, result, request, response, spider):
if not result:
return defer_succeed(None)
it = iter_errback(result, self.handle_spider_error, request, response, spider)
dfd = parallel(it, self.concurrent_items,
self._process_spidermw_output, request, response, spider)
return dfd
result是deferred的返回结果,它是一个生成器,实质是spider中parse方法的返回。
iter_errback是一个包装,用于迭代异常时调用错误处理函数
def iter_errback(iterable, errback, *a, **kw):
"""Wraps an iterable calling an errback if an error is caught while
iterating it.
"""
it = iter(iterable)
while True:
try:
yield next(it)
except StopIteration:
break
except:
errback(failure.Failure(), *a, **kw)
回到handle_spider_output,看一下parallel
# scrapy/utils/defer.py
def parallel(iterable, count, callable, *args, **named):
"""Execute a callable over the objects in the given iterable, in parallel,
using no more than ``count`` concurrent calls.
Taken from: http://jcalderone.livejournal.com/24285.html
"""
coop = task.Cooperator()
work = (callable(elem, *args, **named) for elem in iterable)
return defer.DeferredList([coop.coiterate(work) for _ in range(count)])
它不是很好理解,
关于cooperator()功能见其它文档
要理解的有以下几点:
- coop.coiterate(work)返回的是一个deferred,如果给定的迭代器执行完成或异常,会触发这个deferred的回调;
- 再给这些返回的deferred加上一个包装deferredlist,意为当它们的回调都被触发后会触发deferredlist的回调;
- 对于deferredlist的中多个对象来说,它们意味着cooperator对象中的_task列表有多个worker,而这些worker所指向的迭代器是一致的,即传入的work,所以不存在混乱问题,但work中的每次迭代具体由哪个worker执行及执行顺序是无法保证的,所以work迭代出的执行片段应该具有原子性。
就执行结果而言,count实质上是爬虫的item并发数量限制,_process_spidermw_output实质上是调用itemmanager的process_item,也就是依次调用所有pipeline的process_item处理result迭代出的内容。