• 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

  • 相关阅读:
    Zabbix学习记录
    json_encode 函数使用中报错提示缺少两个参数
    MAC 上搭建一个本地LNMP环境学习laravel(一)
    mysql root 密码重置
    phpstorm格式化代码导致模板报错
    Get 和 Post
    axios设置请求超时时间 timeout
    charles安装配置 for Mac
    让 div 的高度等于宽度,的小技巧
    Git命令行删除本地和远程分支
  • 原文地址:https://www.cnblogs.com/qiu-hua/p/12930707.html
Copyright © 2020-2023  润新知