上一篇提到在Crawler的crawl方法中,启动了Engine。而如Scrapy流程图中描述的,抓取的流程由Engine主导完成。engine是ExecutionEngine的实例,初始化时会初始化诸如scheduler、downloader、scraper(itempipeline和spidermiddleware这一块)等属性。
engine.py所在的目录,抓取流程中的主要模块。
ExecutionEngine的__init__方法,初始化抓取流程中用到的各个控件。
还是从上一章里的open_spider和start两个方法来看流程。
(1)open_spider
首先会根据spider对象里的start_urls获取到初始链接,然后由scheduler来对初始链接进行调度,在这一步之前,仍会经过spider中间件的process_start_requests方法(记得有些教程说的是直接从spider这儿取链接到scheduler,没有提到spider中间件)。
这个slot对象可以看做是请求的状态,来管理engine的运行状态和正在请求的request的管理。而所谓的scheduler对request的操作,则都是通过slot来完成的。
@defer.inlineCallbacks
def open_spider(self, spider, start_requests=(), close_if_idle=True):
assert self.has_capacity(), "No free spider slot when opening %r" % \
spider.name
logger.info("Spider opened", extra={'spider': spider})
nextcall = CallLaterOnce(self._next_request, spider)
scheduler = self.scheduler_cls.from_crawler(self.crawler)
start_requests = yield self.scraper.spidermw.process_start_requests(start_requests, spider)
slot = Slot(start_requests, close_if_idle, nextcall, scheduler) # 使用scheduler初始化slot
self.slot = slot
self.spider = spider
yield scheduler.open(spider) # 对scheduler的初始化(spider,队列, 过滤器)
yield self.scraper.open_spider(spider) # 初始化scraper中的spider,后续分配任务用
self.crawler.stats.open_spider(spider) # 状态收集器,字典方式保存数据。默认是MemoryStatsCollector
yield self.signals.send_catch_log_deferred(signals.spider_opened, spider=spider)
slot.nextcall.schedule() # 请求入队操作
slot.heartbeat.start(5) # 下次请求的时间,又因为默认是即时触发方法,所以并不会等待5秒。
self.heartbeat = task.LoopingCall(nextcall.schedule)
slot.heartbeat.start(5)
在这里设置的下次运行的时间间隔为5秒,可我们在使用scrapy时并没有感受到5秒的间隔,因为在start方法中now默认设置为True,会即时执行。
(2)start
这儿并没有比较重要的启动代码;记录了时间,将状态进行了设置。并创建了一个deferred的对象,会在关闭engine时调用。
@defer.inlineCallbacks
def start(self):
"""Start the execution engine"""
assert not self.running, "Engine already running"
self.start_time = time()
yield self.signals.send_catch_log_deferred(signal=signals.engine_started)
self.running = True
self._closewait = defer.Deferred()
yield self._closewait