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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM