scrapy啓動過程源碼分析

看了上一節分析的scrapy架構,咱們最想了解的,應該就是5大核心模塊是怎麼實現的吧。好,從github中找到各大核心模塊的源碼:html

  • (1) Engine:scrapy/scrapy/core/engine.py
  • (2) Scheduler:scrapy/scrapy/core/scheduler.py
  • (3) Downloader:scrapy/scrapy/core/downloader/
  • (4) Spider:scrapy/scrapy/spider.py
  • (5) Item pipeline:scrapy/scrapy/pipelines/

這些模塊,不是用class實現的,就是用package實現的。看模塊代碼頂多能瞭解它們的功能(成員函數),根本看不出scrapy是怎麼運行的。那麼問題來了,scrapy是怎麼被啓動並運行的?python

一個scrapy程序的運行過程

從(1)中咱們能夠看到,咱們本身寫一個scrapy程序,這個程序的運行,是從命令scrapy crawl hello開始的。 
那這條命令到底作了哪些事呢?react

crawl命令的運行

scrapy程序的源代碼結構是很是清晰的,能夠「猜」到crawl命令的代碼在這裏:git

https://github.com/scrapy/scrapy/blob/master/scrapy/commands/crawl.py
  •  

看樣子是用command模式寫的,命令會執行run()中的代碼,去掉異常處理部分,其實crawl命令最核心的代碼就是下面兩條:github

self.crawler_process.crawl(spname, **opts.spargs)
self.crawler_process.start()

只要能找到self.crawler_process.start()的代碼,就能搞清楚crawl命令的運行細節了。要搞明白這兩條代碼作了什麼,必須知道crawler_process是什麼。在github中搜一下這個crawler_process ,發現編程

cmd.crawler_process = CrawlerProcess(settings)

繼續搜CrawlerProcess(github的搜索功能用起來真是爽)。終於在scrapy/scrapy/crawler.py裏發現了咱們須要尋找的CrawlerProcess.start()函數。設計模式

CrawlerProcess.start()的邏輯

經過上面的分析,scrapy程序的啓動,就是執行了self.crawler_process.start(),跟進去發現實際上是調用了CrawlerProcess.start()函數。從代碼註釋,可看到這個函數作了那麼幾件事:api

  • (1) 初始化一個Twisted reactor對象
  • (2) 調整這個reactor對象的線程池大小
  • (3) 建立一個DNS緩存,並調整緩存大小
  • (4) 判斷是否有其餘爬蟲沒跑結束的,必須等全部爬蟲跑結束才啓動該爬蟲
  • (5) 而後運行reactor.run(installSignalHandlers=False)

而後這個reactor就run了,嗯?!這個reactor是什麼鳥,它run啥內容了呢?緩存

在這個crawler.py文件中看了一圈,也沒發現往reactor中傳入什麼handler之類的參數。不過卻是發現shutdown/kill一個reactor是很容易的。網絡

代碼跟到這裏,必需要看一下Twisted框架的reactor對象運行機制是怎麼樣的了。

Twisted reactor對象

Twisted是python的異步網絡編程框架。做爲一個框架,它有本身的編程套路。這個套路,就是傳說中的「異步編程模式/事件驅動模式」。 事件驅動的特色是包含一個事件循環(loop),當外部事件發生時用回調機制來觸發相應的事件處理代碼。 
reactor,就是I/O併發模型中「reactor模式」的實現。從(3)中可知,reactor就實現了這個事件環(loop)。 
而最重要的是,reactor模式,實現了單線程環境中,調度多個事件源產生的事件到它們各自的事件處理例程中去。藉助這個reactor模式,用單線程,就實現了事件處理機制(callback)。

總結一下,reactor只是一種設計模式,就是一個代碼框架而已。真正的代碼邏輯,應該在調用reactor.run()以前就搞定了的。因此想弄明白CrawlerProcess.start(),得看這個函數調用以前,作了哪些事。

能作啥事呢?也就是CrawlerProcess類,以及它父類的初始化。

CrawlerProcess類

CrawlerProcess類的父類是CrawlerRunner類。看看它們的初始化工做都作了啥。

CrawlerRunner類

看它的註釋,「keeps track of, manages and runs crawlers inside an already setup Twisted reactor」。看到了吧,它就是管理reactor中的各個爬蟲的。 
這個類的初始化,主要代碼就是下面兩行:

self.settings = settings
self.spider_loader = _get_spider_loader(settings)

加載配置,並根據配置加載spider。所謂加載spider,從scrapy/scrapy/spiderloader.py能夠看出,就是加載各個sipder的屬性(name等等)。

CrawlerProcess類

父類初始化後,子類CrawlerProcess才執行初始化。而CrawlerProcess類的功能,根據註釋:run multiple scrapy crawlers in a process simultaneously。就是在單進程中跑多個爬蟲(用twisted的reactor實現)。這個類裏就實現了scrapy的「異步編程模式/事件驅動模式」。

它之因此叫xxxProcess,做者想說的就是「單進程」的意思吧。

這個類除了實現reactor模式,還添加了logshutdown信號處理(Ctrl+C)功能。

總結

理一下,當咱們運行scrapy crawl hello命令,就啓動了一個scrapy爬蟲,它的啓動過程是這樣的:

  • (1) 加載用戶配置,加載全部spider(父類CrawlerRunner類初始化)
  • (2) 初始化log,與shutdown(Ctrl+C)信號處理機制(子類CrawlerProcess類初始化)
  • (3) 運行CrawlerProcess.start(),依次完成下面的邏輯: 
    • (3.1) 初始化一個Twisted reactor對象,調整這個reactor對象的線程池大小
    • (3.2) 建立一個DNS緩存,並調整緩存大小
    • (3.3) 判斷是否有其餘爬蟲沒跑結束的,必須等全部爬蟲跑結束才啓動當前爬蟲
  • (4) 而後運行reactor.run()。至此,第一步加載的spider,都放在reactor模式中運行了。

每一個spider有本身的namestart_urls等屬性。根據(4),scrapy會爲每個URL建立scrapy.Request對象,並將spiderparse()方法做爲scrapy.Request對象的callback函數。

參考

相關文章
相關標籤/搜索