scrapy 源码解析 (二):启动流程源码分析(二) CrawlerProcess主进程

CrawlerProcess主进程

它控制了twisted的reactor,也就是整个事件循环。它负责配置reactor并启动事件循环,最后在所有爬取结束后停止reactor。
另外还控制了一些信号操作,使用户可以手动终止爬取任务。

此类在scrapy/crawler.py中定义,此模块有三个类:Crawler、CrawlerRunner和CrawlerProcess。
Crawler代表了一种爬取任务,里面使用一种spider,CrawlerProcess可以控制多个Crawler同时进行多种爬取任务。
CrawlerRunner是CrawlerProcess的父类,CrawlerProcess通过实现start方法来启动一个Twisted的reactor(另有shutdown信号处理、顶层logging功能)。

CrawlerProcess初始化

首先在命令行启动调用crawl()和start()运行之前,就已经建立了CrawlerProcess对象。

scrapy/crawler.py#CrawlerProcess:

class CrawlerProcess(CrawlerRunner):
    def __init__(self, settings=None, install_root_handler=True):
        super(CrawlerProcess, self).__init__(settings)
        install_shutdown_handlers(self._signal_shutdown)
        configure_logging(self.settings, install_root_handler)
        log_scrapy_info(self.settings)

初始化动作有:
1.使用settings初始化父类CrawlerRunner,只是定义了一些空变量。
2.注册shutdown信号。
3.配置顶层logging。

CrawlerProcess.crawl()创建Crawler对象

在运行前调用了crawl()方法。

scrapy/crawler.py#CrawlerRunner:

    def crawl(self, crawler_or_spidercls, *args, **kwargs):
        crawler = self.create_crawler(crawler_or_spidercls)
        return self._crawl(crawler, *args, **kwargs)

    def _crawl(self, crawler, *args, **kwargs):
        self.crawlers.add(crawler)
        d = crawler.crawl(*args, **kwargs)
        self._active.add(d)
        def _done(result):
            self.crawlers.discard(crawler)
            self._active.discard(d)
            return result
        return d.addBoth(_done)

    def create_crawler(self, crawler_or_spidercls):
        if isinstance(crawler_or_spidercls, Crawler):
            return crawler_or_spidercls
        return self._create_crawler(crawler_or_spidercls)

    def _create_crawler(self, spidercls):
        if isinstance(spidercls, six.string_types):
            spidercls = self.spider_loader.load(spidercls)
        return Crawler(spidercls, self.settings)
View Code

这里需要分清crawler对象和spider对象:
spider是程序员编写的爬虫代码模块,一般是存放在项目里spiders文件夹内,并给每个爬虫模块赋予独立的名称,命令行启动时通过不同的名称启动不同的spider;
crawler是爬取任务,每次在命令行启动,都会新建一个新的crawler爬取任务,可以为同一个spider新建多个crawler,表现在命令里就是同样的命令可以重复执行多次,同一个spider对应的多个crawler共同占有同样的私有配置、同一个任务队列。

crawl()方法执行的动作有:
1.如果crawler_or_spidercls(命令行输入的spider名称)是一个Spider的子类(已经运行)则创建一个新的Crawler,如果crawler_or_spidercls是一个字符串(未运行),则根据名称来查找对应的spider并创建一个Crawler实例并执行Crawler的初始化。
2.返回一个Deferred对象给CrawlerProcess,把Deferred对象加入_active集合,然后就可以在必要时结束Crawler,并通过向Deferred中添加_done callback来跟踪一个Crawler的结束。

Crawler对象初始化

scrapy/crawler.py#Crawler:

class Crawler(object):

    def __init__(self, spidercls, settings=None):
        if isinstance(settings, dict) or settings is None:
            settings = Settings(settings)

        self.spidercls = spidercls
        self.settings = settings.copy()
        self.spidercls.update_settings(self.settings)

        d = dict(overridden_settings(self.settings))
        logger.info("Overridden settings: %(settings)r", {'settings': d})

        self.signals = SignalManager(self)
        self.stats = load_object(self.settings['STATS_CLASS'])(self)

        handler = LogCounterHandler(self, level=self.settings.get('LOG_LEVEL'))
        logging.root.addHandler(handler)
        if get_scrapy_root_handler() is not None:
            # scrapy root handler already installed: update it with new settings
            install_scrapy_root_handler(self.settings)
        # lambda is assigned to Crawler attribute because this way it is not
        # garbage collected after leaving __init__ scope
        self.__remove_handler = lambda: logging.root.removeHandler(handler)
        self.signals.connect(self.__remove_handler, signals.engine_stopped)

        lf_cls = load_object(self.settings['LOG_FORMATTER'])
        self.logformatter = lf_cls.from_crawler(self)
        self.extensions = ExtensionManager.from_crawler(self)

        self.settings.freeze()
        self.crawling = False
        self.spider = None
        self.engine = None

动作:
1.导入spider的custom_settings;
2.添加SignalManager(利用开源的python库pydispatch作消息的发送和路由, scrapy使用它发送关键的消息事件给关心者,如爬取开始,爬取结束等消息。通过send_catch_log_deferred来发送消息,通过connect方法来注册消息的处理函数);
3.添加ExtensionManager。

Crawler.crawl()创建spider对象

scrapy/crawler.py#Crawler:

    @defer.inlineCallbacks
    def crawl(self, *args, **kwargs):
        assert not self.crawling, "Crawling already taking place"
        self.crawling = True
        try:
            self.spider = self._create_spider(*args, **kwargs)
            self.engine = self._create_engine()
            start_requests = iter(self.spider.start_requests())
            yield self.engine.open_spider(self.spider, start_requests)
            yield defer.maybeDeferred(self.engine.start)
        except Exception:
            if six.PY2:
                exc_info = sys.exc_info()
            self.crawling = False
            if self.engine is not None:
                yield self.engine.close()
            if six.PY2:
                six.reraise(*exc_info)
            raise
View Code

调用Crawler的crawl方法(这里使用了Twisted的defer.inlineCallbacks装饰器,表明此函数非阻塞,异步执行)开启一个爬取任务,通过调用spider的from_crawler方法来创建一个spider对象,这样,许多spider类都可以使用crawler的关键方法和数据,属于依赖注入。
spider的代码由程序员自己编写,不同的爬虫类除了调用父类的from_crawler外,可以重定义这个方法来实现个性化实现。
典型写法为:

    @classmethod
    def from_crawler(cls, crawler):
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

Crawler.crawl()创建ExecutionEngine执行引擎以及对应的调度器、下载器、刮取器
继续向下执行:
1.self.engine = self._create_engine(),创建一个ExecutionEngine执行引擎;
ExecutionEngine的初始化代码为:
scrapy/core/engine.py#ExecutionEngine:

class ExecutionEngine(object):

    def __init__(self, crawler, spider_closed_callback):
        self.crawler = crawler
        self.settings = crawler.settings
        self.signals = crawler.signals
        self.logformatter = crawler.logformatter
        self.slot = None
        self.spider = None
        self.running = False
        self.paused = False
        self.scheduler_cls = load_object(self.settings['SCHEDULER'])
        downloader_cls = load_object(self.settings['DOWNLOADER'])
        self.downloader = downloader_cls(crawler)
        self.scraper = Scraper(crawler)
        self._spider_closed_callback = spider_closed_callback

操作有:使用crawler的信号管理器,用来发送注册消息;根据配置加载调度类模块,默认是scrapy.core.scheduler.Scheduler;根据配置加载下载类模块,并创建一个对象,默认是scrapy.core.downloader.Downloade;创建一个Scraper刮取器,主要是用来处理下载后的结果并存储提取的数据。
2.start_requests = iter(self.spider.start_requests()),获取spider中的start_requests;
3.yield self.engine.open_spider(self.spider, start_requests),调用执行引擎打开spider;
4.yield defer.maybeDeferred(self.engine.start),启动执行引擎。此时仍然并未真正开始爬取,仍然是CrawlerProcess.start()之前的预处理步骤。

ExecutionEngine的详细内容将在下一篇讲解。

CrawlerProcess.start()

正式运行。

scrapy/crawler.py#CrawlerProcess:

    def start(self, stop_after_crawl=True):
        if stop_after_crawl:
            d = self.join()
            # Don't start the reactor if the deferreds are already fired
            if d.called:
                return
            d.addBoth(self._stop_reactor)

        reactor.installResolver(self._get_dns_resolver())
        tp = reactor.getThreadPool()
        tp.adjustPoolsize(maxthreads=self.settings.getint('REACTOR_THREADPOOL_MAXSIZE'))
        reactor.addSystemEventTrigger('before', 'shutdown', self.stop)
        reactor.run(installSignalHandlers=False)  # blocking call

1.此函数首先调用join函数来对前面所有Crawler的crawl方法返回的Deferred对象添加一个_stop_reactor方法,当所有Crawler对象都结束时用来关闭reactor。
2.reactor.installResolver安装一个dns缓存。
3.根据配置调整reactor的线程池。
4.添加结束事件的监听。
5.启动reactor事件循环,标志着所有爬虫正式运行,如果没有手动结束,就只会在所有爬虫全部爬取完成后才会自动结束。

————————————————
版权声明:本文为CSDN博主「csdn_yym」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/csdn_yym/java/article/details/85423656

原文地址:https://www.cnblogs.com/qiu-hua/p/12930707.html