Scarpy源码分析5

2021SC@SDUSC

3.3 ExecutionEngine

看下 open_spider

# scrapy/core/engine.py

class ExecutionEngine(object):
    # ...

    @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)
        self.slot = slot
        self.spider = spider
        yield scheduler.open(spider)
        yield self.scraper.open_spider(spider)
        self.crawler.stats.open_spider(spider)
        yield self.signals.send_catch_log_deferred(signals.spider_opened, spider=spider)
        slot.nextcall.schedule()
        slot.heartbeat.start(5)
  • 首先检查 engine 的 slot 是否空闲,这里 1 个 engine 只能运行 1 个 spider
  • 然后是一些属性的初始化以及启动,我们可以分成 3 部分来看:一是 slot 里面的属性,二是 crawler 的属性,最后就是 engine 的直接属性

先了解下上面出现的 CallLaterOnce 的用法

# scrapy/utils/reactor.py

class CallLaterOnce(object):

    def __init__(self, func, *a, **kw):
        self._func = func
        self._a = a
        self._kw = kw
        self._call = None

    def schedule(self, delay=0):
        if self._call is None:
            self._call = reactor.callLater(delay, self)

    def cancel(self):
        if self._call:
            self._call.cancel()

    def __call__(self):
        self._call = None
        return self._func(*self._a, **self._kw)

这个类的目的其实就是实现 callLater 的功能,不同的是我们可以 schedule 多次,在一定时间内只会调度 1 个任务到事件循环里面去,如果直接使用 callLater 的话,会重复添加很多个任务到事件循环里面去,影响执行效率。看个例子

def f(arg):
    # 请求等 IO 操作

nextcall = CallLaterOnce(f, 'some arg')
nextcall.schedule(5)
nextcall.schedule(5)
nextcall.schedule(5)

这里 nextcall 虽说被 schedule 了多次,但是只有第 1 次的 schedule 是有效的,最终只添加了 1 个任务到事件循环。Scrapy 很多地方都会调用 _next_request ,内部又都是并发操作,难免会碰到短时间内同时调用这个方法,这里便使用了 CallLaterOnce 降低处理压力。

看下 Slot 的构造函数

# scrapy/core/engine.py

class Slot(object):

    def __init__(self, start_requests, close_if_idle, nextcall, scheduler):
        self.closing = False
        self.inprogress = set() # requests in progress
        self.start_requests = iter(start_requests)
        self.close_if_idle = close_if_idle
        self.nextcall = nextcall
        self.scheduler = scheduler
        self.heartbeat = task.LoopingCall(nextcall.schedule)
  • inprogress,记录当前正在处理的请求
  • start_requests,爬虫的初始化请求
  • nextcall,封装了 _next_request 方法的 CallLaterOnce
  • scheduler,调度器,里面存放待爬取的请求
  • heartbeat,心跳,每隔一段时间执行一次 _next_request

简单看下 Scheduler 的构造方法

# scrapy/core/scheduler.py

class Scheduler(object):

    def __init__(self, dupefilter, jobdir=None, dqclass=None, mqclass=None,
                 logunser=False, stats=None, pqclass=None, crawler=None):
        self.df = dupefilter
        self.dqdir = self._dqdir(jobdir)
        self.pqclass = pqclass
        self.dqclass = dqclass
        self.mqclass = mqclass
        self.logunser = logunser
        self.stats = stats
        self.crawler = crawler
  • df,去重器
  • dqdir,启动爬虫时,我们指定的持久化目录 jobdir
  • pqclass,优先级队列类型
  • dqclass、mqclass 分别是磁盘和内存队列类型。当我们指定了 jobdir 后,主要是用磁盘队列
  • stats,主要用于统计内存状态,在 Crawler 的构造函数中开始被创建,然后一级一级的传到这来的

上面的类具体是是指那些类,可以看 Scheduler 的 from_crawler 方法。关于怎么入队列、出队列等比较详细的操作,这里不展开,感兴趣的可以看下这个类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值