自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Scrapy源碼剖析:Scrapy有哪些核心組件?

開發(fā) 后端
這篇文章,我們就來進(jìn)一步剖析一下,Scrapy 有哪些核心組件?以及它們主要負(fù)責(zé)了哪些工作?這些組件為了完成這些功能,內(nèi)部又是如何實(shí)現(xiàn)的。

 在上一篇文章:Scrapy源碼剖析:Scrapy是如何運(yùn)行起來的?我們主要剖析了 Scrapy 是如何運(yùn)行起來的核心邏輯,也就是在真正執(zhí)行抓取任務(wù)之前,Scrapy 都做了哪些工作。

這篇文章,我們就來進(jìn)一步剖析一下,Scrapy 有哪些核心組件?以及它們主要負(fù)責(zé)了哪些工作?這些組件為了完成這些功能,內(nèi)部又是如何實(shí)現(xiàn)的。

爬蟲類

我們接著上一篇結(jié)束的地方開始講起。上次講到 Scrapy 運(yùn)行起來后,執(zhí)行到最后到了 Crawler 的 crawl 方法,我們來看這個(gè)方法: 

  1. @defer.inlineCallbacks  
  2. def crawl(self, *args, **kwargs):  
  3.     assert not self.crawling, "Crawling already taking place"  
  4.     self.crawling = True  
  5.     try: 
  6.         # 從spiderloader中找到爬蟲類 并實(shí)例化爬蟲實(shí)例  
  7.         selfself.spider = self._create_spider(*args, **kwargs)  
  8.         # 創(chuàng)建引擎  
  9.         selfself.engine = self._create_engine()  
  10.         # 調(diào)用爬蟲類的start_requests方法 拿到種子URL列表  
  11.         start_requests = iter(self.spider.start_requests())  
  12.         # 執(zhí)行引擎的open_spider 并傳入爬蟲實(shí)例和初始請(qǐng)求  
  13.         yield self.engine.open_spider(self.spider, start_requests)  
  14.         yield defer.maybeDeferred(self.engine.start)  
  15.     except Exception:  
  16.         if six.PY2:  
  17.             exc_info = sys.exc_info()  
  18.         self.crawling = False  
  19.         if self.engine is not None:  
  20.             yield self.engine.close()  
  21.         if six.PY2:  
  22.             six.reraise(*exc_info)  
  23.         raise 

執(zhí)行到這里,我們看到首先創(chuàng)建了爬蟲實(shí)例,然后創(chuàng)建了引擎,最后把爬蟲交給引擎來處理了。

在上一篇文章我們也講到,在 Crawler 實(shí)例化時(shí),會(huì)創(chuàng)建 SpiderLoader,它會(huì)根據(jù)我們定義的配置文件 settings.py 找到存放爬蟲的位置,我們寫的爬蟲代碼都在這里。

然后 SpiderLoader 會(huì)掃描這些代碼文件,并找到父類是 scrapy.Spider 爬蟲類,然后根據(jù)爬蟲類中的 name 屬性(在編寫爬蟲時(shí),這個(gè)屬性是必填的),生成一個(gè) {spider_name: spider_cls} 的字典,最后根據(jù) scrapy crawl <spider_name> 命令中的 spider_name 找到我們寫的爬蟲類,然后實(shí)例化它,在這里就是調(diào)用了_create_spider方法: 

  1. def _create_spider(self, *args, **kwargs):  
  2.     # 調(diào)用類方法from_crawler實(shí)例化  
  3.     return self.spidercls.from_crawler(self, *args, **kwargs) 

實(shí)例化爬蟲比較有意思,它不是通過普通的構(gòu)造方法進(jìn)行初始化,而是調(diào)用了類方法 from_crawler 進(jìn)行的初始化,找到 scrapy.Spider 類: 

  1. @classmethod  
  2. def from_crawler(cls, crawler, *args, **kwargs):  
  3.     spider = cls(*args, **kwargs)  
  4.     spider._set_crawler(crawler)  
  5.     return spider      
  6. def _set_crawler(self, crawler):  
  7.     self.crawler = crawler  
  8.     # 把settings對(duì)象賦給spider實(shí)例  
  9.     self.settings = crawler.settings  
  10.     crawler.signals.connect(self.close, signals.spider_closed) 

在這里我們可以看到,這個(gè)類方法其實(shí)也是調(diào)用了構(gòu)造方法,進(jìn)行實(shí)例化,同時(shí)也拿到了 settings 配置,來看構(gòu)造方法干了些什么? 

  1. class Spider(object_ref):  
  2.     name = None 
  3.     custom_settings = None  
  4.     def __init__(self, name=None, **kwargs):  
  5.         # name必填  
  6.         if name is not None:  
  7.             self.name = name  
  8.         elif not getattr(self, 'name', None):  
  9.             raise ValueError("%s must have a name" % type(self).__name__)  
  10.         self.__dict__.update(kwargs) 
  11.         # 如果沒有設(shè)置start_urls 默認(rèn)是[]  
  12.         if not hasattr(self, 'start_urls'):  
  13.             self.start_urls = [] 

看到這里是不是很熟悉?這里就是我們平時(shí)編寫爬蟲類時(shí),最常用的幾個(gè)屬性:name、start_urls、custom_settings:

  •  name:在運(yùn)行爬蟲時(shí)通過它找到我們編寫的爬蟲類;
  •  start_urls:抓取入口,也可以叫做種子URL;
  •  custom_settings:爬蟲自定義配置,會(huì)覆蓋配置文件中的配置項(xiàng);

引擎

分析完爬蟲類的初始化后,還是回到 Crawler 的 crawl 方法,緊接著就是創(chuàng)建引擎對(duì)象,也就是 _create_engine 方法,看看初始化時(shí)都發(fā)生了什么? 

  1. class ExecutionEngine(object):  
  2.     """引擎"""  
  3.     def __init__(self, crawler, spider_closed_callback):  
  4.         self.crawler = crawler  
  5.         # 這里也把settings配置保存到引擎中  
  6.         self.settings = crawler.settings  
  7.         # 信號(hào)  
  8.         self.signals = crawler.signals  
  9.         # 日志格式  
  10.         self.logformatter = crawler.logformatter  
  11.         self.slot = None  
  12.         self.spider = None  
  13.         self.running = False  
  14.         self.paused = False  
  15.         # 從settings中找到Scheduler調(diào)度器,找到Scheduler類  
  16.         self.scheduler_cls = load_object(self.settings['SCHEDULER']) 
  17.         # 同樣,找到Downloader下載器類  
  18.         downloader_cls = load_object(self.settings['DOWNLOADER'])  
  19.         # 實(shí)例化Downloader  
  20.         self.downloader = downloader_cls(crawler)  
  21.         # 實(shí)例化Scraper 它是引擎連接爬蟲類的橋梁  
  22.         self.scraper = Scraper(crawler)  
  23.         self._spider_closed_callback = spider_closed_callback 

在這里我們能看到,主要是對(duì)其他幾個(gè)核心組件進(jìn)行定義和初始化,主要包括包括:Scheduler、Downloader、Scrapyer,其中 Scheduler 只進(jìn)行了類定義,沒有實(shí)例化。

也就是說,引擎是整個(gè) Scrapy 的核心大腦,它負(fù)責(zé)管理和調(diào)度這些組件,讓這些組件更好地協(xié)調(diào)工作。

下面我們依次來看這幾個(gè)核心組件都是如何初始化的?

調(diào)度器

調(diào)度器初始化發(fā)生在引擎的 open_spider 方法中,我們提前來看一下調(diào)度器的初始化。 

  1. class Scheduler(object):  
  2.  """調(diào)度器"""  
  3.     def __init__(self, dupefilter, jobdir=Nonedqclass=Nonemqclass=None 
  4.                  logunser=Falsestats=Nonepqclass=None):  
  5.         # 指紋過濾器  
  6.         self.df = dupefilter  
  7.         # 任務(wù)隊(duì)列文件夾  
  8.         selfself.dqdir = self._dqdir(jobdir)  
  9.         # 優(yōu)先級(jí)任務(wù)隊(duì)列類  
  10.         self.pqclass = pqclass  
  11.         # 磁盤任務(wù)隊(duì)列類  
  12.         self.dqclass = dqclass  
  13.         # 內(nèi)存任務(wù)隊(duì)列類  
  14.         self.mqclass = mqclass  
  15.         # 日志是否序列化  
  16.         self.logunser = logunser  
  17.         self.stats = stats       
  18.      @classmethod  
  19.     def from_crawler(cls, crawler):  
  20.         settings = crawler.settings  
  21.         # 從配置文件中獲取指紋過濾器類  
  22.         dupefilter_cls = load_object(settings['DUPEFILTER_CLASS'])  
  23.         # 實(shí)例化指紋過濾器  
  24.         dupefilter = dupefilter_cls.from_settings(settings)  
  25.         # 從配置文件中依次獲取優(yōu)先級(jí)任務(wù)隊(duì)列類、磁盤隊(duì)列類、內(nèi)存隊(duì)列類  
  26.         pqclass = load_object(settings['SCHEDULER_PRIORITY_QUEUE'])  
  27.         dqclass = load_object(settings['SCHEDULER_DISK_QUEUE'])  
  28.         mqclass = load_object(settings['SCHEDULER_MEMORY_QUEUE'])  
  29.         # 請(qǐng)求日志序列化開關(guān)  
  30.         logunser = settings.getbool('LOG_UNSERIALIZABLE_REQUESTS', settings.getbool('SCHEDULER_DEBUG'))  
  31.         return cls(dupefilter, jobdir=job_dir(settings), logunserlogunser=logunser,  
  32.                    stats=crawler.stats, pqclasspqclass=pqclass, dqclassdqclass=dqclass, mqclassmqclass=mqclass) 

可以看到,調(diào)度器的初始化主要做了 2 件事:

  •  實(shí)例化請(qǐng)求指紋過濾器:主要用來過濾重復(fù)請(qǐng)求;
  •  定義不同類型的任務(wù)隊(duì)列:優(yōu)先級(jí)任務(wù)隊(duì)列、基于磁盤的任務(wù)隊(duì)列、基于內(nèi)存的任務(wù)隊(duì)列;

請(qǐng)求指紋過濾器又是什么?

在配置文件中,我們可以看到定義的默認(rèn)指紋過濾器是 RFPDupeFilter: 

  1. class RFPDupeFilter(BaseDupeFilter):  
  2.     """請(qǐng)求指紋過濾器"""  
  3.     def __init__(self, path=Nonedebug=False):  
  4.         self.file = None  
  5.         # 指紋集合 使用的是Set 基于內(nèi)存  
  6.         self.fingerprints = set()  
  7.         self.logdupes = True  
  8.         self.debug = debug  
  9.         self.logger = logging.getLogger(__name__)  
  10.         # 請(qǐng)求指紋可存入磁盤  
  11.         if path:  
  12.             self.file = open(os.path.join(path, 'requests.seen'), 'a+') 
  13.              self.file.seek(0)  
  14.             self.fingerprints.update(x.rstrip() for x in self.file) 
  15.     @classmethod  
  16.     def from_settings(cls, settings):  
  17.         debug = settings.getbool('DUPEFILTER_DEBUG')  
  18.         return cls(job_dir(settings), debug) 

請(qǐng)求指紋過濾器初始化時(shí),定義了指紋集合,這個(gè)集合使用內(nèi)存實(shí)現(xiàn)的 Set,而且可以控制這些指紋是否存入磁盤以供下次重復(fù)使用。

也就是說,指紋過濾器的主要職責(zé)是:過濾重復(fù)請(qǐng)求,可自定義過濾規(guī)則。

在下篇文章中我們會(huì)介紹到,每個(gè)請(qǐng)求是根據(jù)什么規(guī)則生成指紋的,然后是又如何實(shí)現(xiàn)重復(fù)請(qǐng)求過濾邏輯的,這里我們先知道它的功能即可。

下面來看調(diào)度器定義的任務(wù)隊(duì)列都有什么作用?

調(diào)度器默認(rèn)定義了 2 種隊(duì)列類型:

  •  基于磁盤的任務(wù)隊(duì)列:在配置文件可配置存儲(chǔ)路徑,每次執(zhí)行后會(huì)把隊(duì)列任務(wù)保存到磁盤上;
  •  基于內(nèi)存的任務(wù)隊(duì)列:每次都在內(nèi)存中執(zhí)行,下次啟動(dòng)則消失;

配置文件默認(rèn)定義如下: 

  1. # 基于磁盤的任務(wù)隊(duì)列(后進(jìn)先出)  
  2. SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleLifoDiskQueue'  
  3. # 基于內(nèi)存的任務(wù)隊(duì)列(后進(jìn)先出)  
  4. SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.LifoMemoryQueue'  
  5. # 優(yōu)先級(jí)隊(duì)列  
  6. SCHEDULER_PRIORITY_QUEUE = 'queuelib.PriorityQueue' 

如果我們?cè)谂渲梦募卸x了 JOBDIR 配置項(xiàng),那么每次執(zhí)行爬蟲時(shí),都會(huì)把任務(wù)隊(duì)列保存在磁盤中,下次啟動(dòng)爬蟲時(shí)可以重新加載繼續(xù)執(zhí)行我們的任務(wù)。

如果沒有定義這個(gè)配置項(xiàng),那么默認(rèn)使用的是內(nèi)存隊(duì)列。

細(xì)心的你可能會(huì)發(fā)現(xiàn),默認(rèn)定義的這些隊(duì)列結(jié)構(gòu)都是后進(jìn)先出的,什么意思呢?

也就是在運(yùn)行我們的爬蟲代碼時(shí),如果生成一個(gè)抓取任務(wù),放入到任務(wù)隊(duì)列中,那么下次抓取就會(huì)從任務(wù)隊(duì)列中先獲取到這個(gè)任務(wù),優(yōu)先執(zhí)行。

這么實(shí)現(xiàn)意味什么呢?其實(shí)意味著:Scrapy 默認(rèn)的采集規(guī)則是深度優(yōu)先!

如何改變這種機(jī)制,變?yōu)閺V度優(yōu)先采集呢?這時(shí)候我們就要看一下 scrapy.squeues 模塊了,在這里定義了很多種隊(duì)列: 

  1. # 先進(jìn)先出磁盤隊(duì)列(pickle序列化)  
  2. PickleFifoDiskQueue = _serializable_queue(queue.FifoDiskQueue, \  
  3.     _pickle_serialize, pickle.loads)  
  4. # 后進(jìn)先出磁盤隊(duì)列(pickle序列化)  
  5. PickleLifoDiskQueue = _serializable_queue(queue.LifoDiskQueue, \  
  6.     _pickle_serialize, pickle.loads)  
  7. # 先進(jìn)先出磁盤隊(duì)列(marshal序列化)  
  8. MarshalFifoDiskQueue = _serializable_queue(queue.FifoDiskQueue, \  
  9.     marshal.dumps, marshal.loads)  
  10. # 后進(jìn)先出磁盤隊(duì)列(marshal序列化)  
  11. MarshalLifoDiskQueue = _serializable_queue(queue.LifoDiskQueue, \  
  12.     marshal.dumps, marshal.loads)  
  13. # 先進(jìn)先出內(nèi)存隊(duì)列  
  14. FifoMemoryQueue = queue.FifoMemoryQueue  
  15. # 后進(jìn)先出內(nèi)存隊(duì)列  
  16. LifoMemoryQueue = queue.LifoMemoryQueue 

如果我們想把抓取任務(wù)改為廣度優(yōu)先,我們只需要在配置文件中把隊(duì)列類修改為先進(jìn)先出隊(duì)列類就可以了!從這里我們也可以看出,Scrapy 各個(gè)組件之間的耦合性非常低,每個(gè)模塊都是可自定義的。

如果你想探究這些隊(duì)列是如何實(shí)現(xiàn)的,可以參考 Scrapy 作者寫的 scrapy/queuelib 項(xiàng)目,在 Github 上就可以找到,在這里有這些隊(duì)列的具體實(shí)現(xiàn)。

下載器

回到引擎的初始化的地方,接下來我們來看,下載器是如何初始化的。

在默認(rèn)的配置文件 default_settings.py 中,下載器配置如下:

  1. DOWNLOADER = 'scrapy.core.downloader.Downloader' 

我們來看 Downloader 類的初始化: 

  1. class Downloader(object):  
  2.     """下載器"""  
  3.     def __init__(self, crawler):  
  4.         # 同樣的 拿到settings對(duì)象  
  5.         self.settings = crawler.settings  
  6.         self.signals = crawler.signals  
  7.         self.slots = {}  
  8.         self.active = set()  
  9.         # 初始化DownloadHandlers  
  10.         self.handlers = DownloadHandlers(crawler)  
  11.         # 從配置中獲取設(shè)置的并發(fā)數(shù)  
  12.         selfself.total_concurrency = self.settings.getint('CONCURRENT_REQUESTS')  
  13.         # 同一域名并發(fā)數(shù)  
  14.         selfself.domain_concurrency = self.settings.getint('CONCURRENT_REQUESTS_PER_DOMAIN')  
  15.         # 同一IP并發(fā)數(shù)  
  16.         selfself.ip_concurrency = self.settings.getint('CONCURRENT_REQUESTS_PER_IP')  
  17.         # 隨機(jī)延遲下載時(shí)間  
  18.         selfself.randomize_delay = self.settings.getbool('RANDOMIZE_DOWNLOAD_DELAY')  
  19.         # 初始化下載器中間件  
  20.         self.middleware = DownloaderMiddlewareManager.from_crawler(crawler)  
  21.         self._slot_gc_loop = task.LoopingCall(self._slot_gc) 
  22.         self._slot_gc_loop.start(60) 

在這個(gè)過程中,主要是初始化了下載處理器、下載器中間件管理器以及從配置文件中拿到抓取請(qǐng)求控制的相關(guān)參數(shù)。

那么下載處理器是做什么的?下載器中間件又負(fù)責(zé)哪些工作?

先來看 DownloadHandlers: 

  1. class DownloadHandlers(object):  
  2.     """下載器處理器"""  
  3.     def __init__(self, crawler):  
  4.         self._crawler = crawler  
  5.         self._schemes = {} # 存儲(chǔ)scheme對(duì)應(yīng)的類路徑 后面用于實(shí)例化  
  6.         self._handlers = {} # 存儲(chǔ)scheme對(duì)應(yīng)的下載器  
  7.         self._notconfigured = {}  
  8.         # 從配置中找到DOWNLOAD_HANDLERS_BASE 構(gòu)造下載處理器  
  9.         # 注意:這里是調(diào)用getwithbase方法  取的是配置中的XXXX_BASE配置  
  10.         handlers = without_none_values
  11.              crawler.settings.getwithbase('DOWNLOAD_HANDLERS'))  
  12.         # 存儲(chǔ)scheme對(duì)應(yīng)的類路徑 后面用于實(shí)例化  
  13.         for scheme, clspath in six.iteritems(handlers):  
  14.             self._schemes[scheme] = clspath  
  15.         crawler.signals.connect(self._close, signals.engine_stopped) 

下載處理器在默認(rèn)的配置文件中是這樣配置的: 

  1. # 用戶可自定義的下載處理器  
  2. DOWNLOAD_HANDLERS = {}  
  3. # 默認(rèn)的下載處理器  
  4. DOWNLOAD_HANDLERS_BASE = {  
  5.     'file': 'scrapy.core.downloader.handlers.file.FileDownloadHandler',  
  6.     'http': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler',  
  7.     'https': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler',  
  8.     's3': 'scrapy.core.downloader.handlers.s3.S3DownloadHandler',  
  9.     'ftp': 'scrapy.core.downloader.handlers.ftp.FTPDownloadHandler',  

看到這里你應(yīng)該能明白了,下載處理器會(huì)根據(jù)下載資源的類型,選擇對(duì)應(yīng)的下載器去下載資源。其中我們最常用的就是 http 和 https 對(duì)應(yīng)的處理器。

但是請(qǐng)注意,在這里,這些下載器是沒有被實(shí)例化的,只有在真正發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí),才會(huì)進(jìn)行初始化,而且只會(huì)初始化一次,后面文章會(huì)講到。

下面我們來看下載器中間件 DownloaderMiddlewareManager 初始化過程,同樣地,這里又調(diào)用了類方法 from_crawler 進(jìn)行初始化,而且 DownloaderMiddlewareManager 繼承了MiddlewareManager 類,來看它在初始化做了哪些工作: 

  1. class MiddlewareManager(object):  
  2.     """所有中間件的父類,提供中間件公共的方法"""  
  3.     component_name = 'foo middleware'  
  4.     @classmethod  
  5.     def from_crawler(cls, crawler):  
  6.         # 調(diào)用from_settings  
  7.         return cls.from_settings(crawler.settings, crawler)  
  8.      @classmethod  
  9.     def from_settings(cls, settings, crawler=None):  
  10.         # 調(diào)用子類_get_mwlist_from_settings得到所有中間件類的模塊  
  11.         mwlist = cls._get_mwlist_from_settings(settings) 
  12.          middlewares = []  
  13.         enabled = []  
  14.         # 依次實(shí)例化  
  15.         for clspath in mwlist:  
  16.             try:  
  17.                 # 加載這些中間件模塊  
  18.                 mwcls = load_object(clspath)  
  19.                 # 如果此中間件類定義了from_crawler 則調(diào)用此方法實(shí)例化  
  20.                 if crawler and hasattr(mwcls, 'from_crawler'):  
  21.                     mw = mwcls.from_crawler(crawler)  
  22.                 # 如果此中間件類定義了from_settings 則調(diào)用此方法實(shí)例化  
  23.                 elif hasattr(mwcls, 'from_settings'): 
  24.                      mw = mwcls.from_settings(settings)  
  25.                 # 上面2個(gè)方法都沒有,則直接調(diào)用構(gòu)造實(shí)例化  
  26.                 else:  
  27.                     mw = mwcls()  
  28.                 middlewares.append(mw)  
  29.                 enabled.append(clspath)  
  30.             except NotConfigured as e:  
  31.                 if e.args:  
  32.                     clsname = clspath.split('.')[-1]  
  33.                     logger.warning("Disabled %(clsname)s: %(eargs)s",  
  34.                                    {'clsname': clsname, 'eargs': e.args[0]},  
  35.                                    extra={'crawler': crawler})   
  36.         logger.info("Enabled %(componentname)ss:\n%(enabledlist)s",  
  37.                     {'componentname': cls.component_name,  
  38.                      'enabledlist': pprint.pformat(enabled)},  
  39.                     extra={'crawler': crawler})  
  40.         # 調(diào)用構(gòu)造方法  
  41.         return cls(*middlewares) 
  42.     @classmethod  
  43.     def _get_mwlist_from_settings(cls, settings):  
  44.         # 具體有哪些中間件類,子類定義  
  45.         raise NotImplementedError 
  46.      def __init__(self, *middlewares):  
  47.         self.middlewares = middlewares  
  48.         # 定義中間件方法  
  49.         self.methods = defaultdict(list)  
  50.         for mw in middlewares:  
  51.             self._add_middleware(mw)      
  52.   def _add_middleware(self, mw):  
  53.         # 默認(rèn)定義的 子類可覆蓋  
  54.         # 如果中間件類有定義open_spider 則加入到methods  
  55.         if hasattr(mw, 'open_spider'):  
  56.             self.methods['open_spider'].append(mw.open_spider)  
  57.         # 如果中間件類有定義close_spider 則加入到methods  
  58.         # methods就是一串中間件的方法鏈 后期會(huì)依次調(diào)用  
  59.         if hasattr(mw, 'close_spider'):  
  60.             self.methods['close_spider'].insert(0, mw.close_spider) 

DownloaderMiddlewareManager 實(shí)例化過程: 

  1. class DownloaderMiddlewareManager(MiddlewareManager):  
  2.  """下載中間件管理器"""  
  3.     component_name = 'downloader middleware'  
  4.     @classmethod  
  5.     def _get_mwlist_from_settings(cls, settings):  
  6.         # 從配置文件DOWNLOADER_MIDDLEWARES_BASE和DOWNLOADER_MIDDLEWARES獲得所有下載器中間件  
  7.         return build_component_list(  
  8.             settings.getwithbase('DOWNLOADER_MIDDLEWARES'))  
  9.     def _add_middleware(self, mw):  
  10.         # 定義下載器中間件請(qǐng)求、響應(yīng)、異常一串方法  
  11.         if hasattr(mw, 'process_request'):  
  12.             self.methods['process_request'].append(mw.process_request)  
  13.         if hasattr(mw, 'process_response'):  
  14.             self.methods['process_response'].insert(0, mw.process_response)  
  15.         if hasattr(mw, 'process_exception'):  
  16.             self.methods['process_exception'].insert(0, mw.process_exception) 

下載器中間件管理器繼承了 MiddlewareManager 類,然后重寫了 _add_middleware 方法,為下載行為定義默認(rèn)的下載前、下載后、異常時(shí)對(duì)應(yīng)的處理方法。

這里我們可以想一下,中間件這么做的好處是什么?

從這里能大概看出,從某個(gè)組件流向另一個(gè)組件時(shí),會(huì)經(jīng)過一系列中間件,每個(gè)中間件都定義了自己的處理流程,相當(dāng)于一個(gè)個(gè)管道,輸入時(shí)可以針對(duì)數(shù)據(jù)進(jìn)行處理,然后送達(dá)到另一個(gè)組件,另一個(gè)組件處理完邏輯后,又經(jīng)過這一系列中間件,這些中間件可再針對(duì)這個(gè)響應(yīng)結(jié)果進(jìn)行處理,最終輸出。

Scraper

下載器實(shí)例化完了之后,回到引擎的初始化方法中,然后就是實(shí)例化 Scraper,在Scrapy源碼分析(一)架構(gòu)概覽這篇文章中我提到過,這個(gè)類沒有在架構(gòu)圖中出現(xiàn),但這個(gè)類其實(shí)是處于Engine、Spiders、Pipeline 之間,是連通這三個(gè)組件的橋梁。

我們來看一下它的初始化過程: 

  1. class Scraper(object):  
  2.     def __init__(self, crawler):  
  3.         self.slot = None  
  4.         # 實(shí)例化爬蟲中間件管理器  
  5.         self.spidermw = SpiderMiddlewareManager.from_crawler(crawler)  
  6.         # 從配置文件中加載Pipeline處理器類  
  7.         itemproc_cls = load_object(crawler.settings['ITEM_PROCESSOR'])  
  8.         # 實(shí)例化Pipeline處理器  
  9.         self.itemproc = itemproc_cls.from_crawler(crawler)  
  10.         # 從配置文件中獲取同時(shí)處理輸出的任務(wù)個(gè)數(shù)  
  11.         self.concurrent_items = crawler.settings.getint('CONCURRENT_ITEMS')  
  12.         self.crawler = crawler  
  13.         self.signals = crawler.signals  
  14.         self.logformatter = crawler.logformatter 

Scraper 創(chuàng)建了 SpiderMiddlewareManager,它的初始化過程: 

  1. class SpiderMiddlewareManager(MiddlewareManager):  
  2.  """爬蟲中間件管理器"""  
  3.     component_name = 'spider middleware'  
  4.     @classmethod  
  5.     def _get_mwlist_from_settings(cls, settings):  
  6.         # 從配置文件中SPIDER_MIDDLEWARES_BASE和SPIDER_MIDDLEWARES獲取默認(rèn)的爬蟲中間件類  
  7.         return build_component_list(settings.getwithbase('SPIDER_MIDDLEWARES')) 
  8.     def _add_middleware(self, mw):  
  9.         super(SpiderMiddlewareManager, self)._add_middleware(mw)  
  10.         # 定義爬蟲中間件處理方法  
  11.         if hasattr(mw, 'process_spider_input'):  
  12.             self.methods['process_spider_input'].append(mw.process_spider_input)  
  13.         if hasattr(mw, 'process_spider_output'):  
  14.             self.methods['process_spider_output'].insert(0, mw.process_spider_output)  
  15.         if hasattr(mw, 'process_spider_exception'):  
  16.             self.methods['process_spider_exception'].insert(0, mw.process_spider_exception)  
  17.         if hasattr(mw, 'process_start_requests'):  
  18.             self.methods['process_start_requests'].insert(0, mw.process_start_requests) 

爬蟲中間件管理器初始化與之前的下載器中間件管理器類似,先是從配置文件中加載了默認(rèn)的爬蟲中間件類,然后依次注冊(cè)爬蟲中間件的一系列流程方法。配置文件中定義的默認(rèn)的爬蟲中間件類如下: 

  1. SPIDER_MIDDLEWARES_BASE = {  
  2.  # 默認(rèn)的爬蟲中間件類  
  3.     'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50,  
  4.     'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500,  
  5.     'scrapy.spidermiddlewares.referer.RefererMiddleware': 700,  
  6.     'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800,  
  7.     'scrapy.spidermiddlewares.depth.DepthMiddleware': 900,  

這里解釋一下,這些默認(rèn)的爬蟲中間件的職責(zé):

  •  HttpErrorMiddleware:針對(duì)非 200 響應(yīng)錯(cuò)誤進(jìn)行邏輯處理;
  •  OffsiteMiddleware:如果Spider中定義了 allowed_domains,會(huì)自動(dòng)過濾除此之外的域名請(qǐng)求;
  •  RefererMiddleware:追加 Referer 頭信息;
  •  UrlLengthMiddleware:過濾 URL 長(zhǎng)度超過限制的請(qǐng)求;
  •  DepthMiddleware:過濾超過指定深度的抓取請(qǐng)求;

當(dāng)然,在這里你也可以定義自己的爬蟲中間件,來處理自己所需的邏輯。

爬蟲中間件管理器初始化完之后,然后就是 Pipeline 組件的初始化,默認(rèn)的 Pipeline 組件是 ItemPipelineManager: 

  1. class ItemPipelineManager(MiddlewareManager):  
  2.     component_name = 'item pipeline'  
  3.     @classmethod  
  4.     def _get_mwlist_from_settings(cls, settings):  
  5.         # 從配置文件加載ITEM_PIPELINES_BASE和ITEM_PIPELINES類  
  6.         return build_component_list(settings.getwithbase('ITEM_PIPELINES'))  
  7.     def _add_middleware(self, pipe):  
  8.         super(ItemPipelineManager, self)._add_middleware(pipe)  
  9.         # 定義默認(rèn)的pipeline處理邏輯  
  10.         if hasattr(pipe, 'process_item'):  
  11.             self.methods['process_item'].append(pipe.process_item) 
  12.     def process_item(self, item, spider):  
  13.         # 依次調(diào)用所有子類的process_item方法  
  14.         return self._process_chain('process_item', item, spider) 

我們可以看到 ItemPipelineManager 也是中間件管理器的一個(gè)子類,由于它的行為非常類似于中間件,但由于功能較為獨(dú)立,所以屬于核心組件之一。

從 Scraper 的初始化過程我們可以看出,它管理著 Spiders 和 Pipeline 相關(guān)的數(shù)據(jù)交互。

總結(jié)

好了,這篇文章我們主要剖析了 Scrapy 涉及到的核心的組件,主要包括:引擎、下載器、調(diào)度器、爬蟲類、輸出處理器,以及它們各自都是如何初始化的,在初始化過程中,它們又包含了哪些子模塊來輔助完成這些模塊的功能。

這些組件各司其職,相互協(xié)調(diào),共同完成爬蟲的抓取任務(wù),而且從代碼中我們也能發(fā)現(xiàn),每個(gè)組件類都是定義在配置文件中的,也就是說我們可以實(shí)現(xiàn)自己的邏輯,然后替代這些組件,這樣的設(shè)計(jì)模式也非常值得我們學(xué)習(xí)。 

 

責(zé)任編輯:龐桂玉 來源: Python中文社區(qū) (ID:python-china)
相關(guān)推薦

2017-05-15 21:00:15

大數(shù)據(jù)Scrapy爬蟲框架

2025-04-01 00:54:00

2022-09-30 10:44:47

Netty組件數(shù)據(jù)

2016-10-26 08:57:13

HadoopScrapy大數(shù)據(jù)

2017-01-15 14:18:35

大數(shù)據(jù)HadoopScrapy

2012-07-17 09:13:14

Scrapy

2018-08-08 11:40:24

ScrapyRequest網(wǎng)絡(luò)爬蟲

2023-08-28 09:14:20

ScrapyPython

2020-12-29 05:34:48

Scrapy網(wǎng)頁源代碼

2021-06-02 15:10:20

PythonScrapy視頻

2021-01-08 09:07:19

Scrapy框架爬蟲

2017-11-29 15:21:53

PythonScrapy爬蟲

2023-08-29 09:31:01

Scrapy網(wǎng)頁爬蟲

2021-02-02 07:37:39

NextTickvueDOM

2017-09-16 17:45:32

數(shù)據(jù)采集Scrapy爬蟲

2020-12-07 11:23:32

Scrapy爬蟲Python

2021-04-12 07:36:15

Scrapy爬蟲框架

2021-11-09 09:46:09

ScrapyPython爬蟲

2021-11-08 14:38:50

框架Scrapy 爬蟲

2016-11-29 09:38:06

Flume架構(gòu)核心組件
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)