Scrapy進階知識點總結(六)——中間件詳解

 

概述

查看scrapy官網的框架圖,能夠看出中間件處於幾大主要組件之間,相似於生產流水線上的加工過程,將原料按照不一樣需求與功能加工成成品html

 

其中4,5處於下載器與引擎之間的就是下載中間件,而spider與引擎之間的就是spider中間件。目前scrapy主要的中間件就這兩個git

 

下載中間件

下載器中間件是介於Scrapy的request/response處理的鉤子框架,是用於全局修改Scrapy request和response的一個輕量、底層的系統。github

主要做用:web

  • 在Scrapy將請求發送到網站以前修改,處理請求,如:更換代理ip,header等
  • 在將響應傳遞給引擎以前處理收到的響應,如:響應失敗從新請求,或將失敗的作必定處理再返回給引擎
  • 忽略一些響應或者請求

默認下載中間件

scrapy內置了一些默認配置,這些是不容許被修改的,一般是_BASE結尾的設置,好比DOWNLOADER_MIDDLEWARES_BASE下載中間件的默認設置,以下ajax

{ 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550, 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600, 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750, 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, }

scrapy就是按照上面數字從小到大依次執行的,好比執行完RobotsTxtMiddleware的process_request()方法後會繼續執行下面HttpAuthMiddleware等process_request(),能夠看做串聯的形式依次過流水線數據庫

若是咱們要添加自定義的下載中間件,須要在settings.py中激活DOWNLOADER_MIDDLEWARES。同時想取消默認的一些中間件,也能夠設置爲None。注意的是激活DOWNLOADER_MIDDLEWARES並不會覆蓋DOWNLOADER_MIDDLEWARES_BASE,而是繼續串聯起來cookie

DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.CustomDownloaderMiddleware': 543, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, }

各默認中間件的能夠參考https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#built-in-downloader-middleware-reference框架

 

自定義下載中間件

在建立項目後,再項目文件夾中有一middlewares.py文件,裏面自動生成了兩個中間件示例或者說模板。咱們若是要自定義中間件的話,能夠在給的示例上修改,或者新建類實現方法,或者繼承已有的中間件重寫方法dom

如下是下載中間件能夠實現的方法,在自定義中間件時,能夠根據需求實現scrapy

1.process_request(self, request, spider)

當每一個request經過下載中間件時,該方法被調用。process_request() 必須返回其中之一: 返回 None 、返回一個 Response 對象、返回一個 Request 對象或raise IgnoreRequest 。 最常使用的是返回None
  • 若是其返回 None ,會將處理事後的request丟給中間件鏈中的下一個中間件的process_request()方法處理,直到丟到下載器,由下載器下載
  • 若是其返回 Response 對象,Scrapy將不會調用任何其餘的 process_request() 或 process_exception() 方法,也不會丟到下載器下載;直接將其返回的response丟到中間件鏈的process_response()處理。能夠經過scrapy.http.Response構建Response 
  • 若是其返回 Request 對象,Scrapy則中止調用process_request方法並從新調度返回的request。當新返回的request被執行後, 相應地中間件鏈將會根據下載的response被調用。
  • 若是其raise一個 IgnoreRequest 異常,則安裝的下載中間件的 process_exception() 方法會被調用。若是沒有任何一個方法處理該異常, 則request的errback(Request.errback)方法會被調用。若是沒有代碼處理拋出的異常, 則該異常被忽略且不記錄(不一樣於其餘異常那樣)。
參數:
  request(Request 對象)–處理的request
  spider(Spider 對象)–該request對應的spider
 
2.process_response(self, request, response, spider)
 
當下載的response返回時,process_response()被調用,且 必須返回如下之一: 返回一個 Response 對象、 返回一個 Request 對象或raise一個 IgnoreRequest 異常。
  • 若是其返回一個 Response (能夠與傳入的response相同,也能夠是全新的對象), 該response會被在鏈中的其餘中間件的 process_response() 方法處理。
  • 若是其返回一個 Request 對象,則中間件鏈中止, 返回的request會被從新調度下載。處理相似於 process_request() 返回request所作的那樣。
  • 若是其拋出一個 IgnoreRequest 異常,則調用request的errback(Request.errback)。 若是沒有代碼處理拋出的異常,則該異常被忽略且不記錄(不一樣於其餘異常那樣)。
參數:
  request (Request 對象) – response所對應的request
  response (Response 對象) – 被處理的response
  spider (Spider 對象) – response所對應的spider
 
 
3.process_exception(self, request, exception, spider)
 
當下載處理器(download handler)或 process_request() (下載中間件)拋出異常(包括IgnoreRequest異常)時,Scrapy調用 process_exception() 。process_exception() 應該返回如下之一: 返回 None 、 一個 Response 對象、或者一個 Request 對象。
  • 若是其返回 None ,Scrapy將會繼續處理該異常,接着調用已安裝的其餘中間件的 process_exception() 方法,直到全部中間件都被調用完畢,則調用默認的異常處理。
  • 若是其返回一個 Response 對象,則已安裝的中間件鏈的 process_response() 方法被調用。Scrapy將不會調用任何其餘中間件的 process_exception() 方法。
  • 若是其返回一個 Request 對象, 則返回的request將會被從新調用下載。這將中止中間件的 process_exception() 方法執行,就如返回一個response的那樣。
參數:
  request (是 Request 對象) – 產生異常的request
  exception (Exception 對象) – 拋出的異常
  spider (Spider 對象) – request對應的spider
 
 
4.from_crawler(cls, crawler)
 
若是存在,則調用此類方法建立中間件實例Crawler。它必須返回一個新的中間件實例。Crawler對象提供對全部Scrapy核心組件的訪問,如設置和信號; 它是中間件訪問它們並將其功能掛鉤到Scrapy的一種方式。

參數:

  crawler(Crawlerobject)- 使用此中間件的爬網程序

 

設置隨機UA中間件

user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
    "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
    "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
    "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
    "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
    "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
    "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5" ] class UserAgent_Middleware():
def process_request(self, request, spider): ua = random.choice(user_agent_list) request.headers['User-Agent'] = ua

或者使用faker大魔王

from faker import Faker class UserAgent_Middleware(): def process_request(self, request, spider): f = Faker() ua = f.firefox() request.headers['User-Agent'] = ua

 

設置代理中間件

proxy_list=[ "http://180.76.154.5:8888", "http://14.109.107.1:8998", "http://106.46.136.159:808", "http://175.155.24.107:808", "http://124.88.67.10:80", "http://124.88.67.14:80", "http://58.23.122.79:8118", "http://123.157.146.116:8123", "http://124.88.67.21:843", "http://106.46.136.226:808", "http://101.81.120.58:8118", "http://180.175.145.148:808"] class proxy_Middleware(object): def process_request(self,request,spider): proxy = random.choice(proxy_list) request.meta['proxy'] = proxy

至於代理池,能夠本身爬取,或者github上查找,或者編寫一套可替換可檢查可用性的代理池腳本存在文件或者數據庫中

 

集成selenium

from selenium import webdriver from scrapy.http import HtmlResponse import time class SeleniumMiddleware(object): def __init__(self): self.driver = webdriver.Chrome() def process_request(self, request, spider): self.driver.get(request.url) time.sleep(2) body = self.driver.page_source return HtmlResponse(self.driver.current_url, body=body, encoding='utf-8', request=request)

固然不是每個spider都要用selenium,那樣會很慢,能夠在spider裏的custom_settings單獨激活這個中間件,selenium的用法會在其餘文章講述

 

重試中間件

from scrapy.downloadermiddlewares.retry import RetryMiddleware from scrapy.utils.response import response_status_message class CustomRetryMiddleware(RetryMiddleware): def process_response(self, request, response, spider): if request.meta.get('dont_retry', False): return response if response.status in self.retry_http_codes: reason = response_status_message(response.status) #若是返回了[500, 502, 503, 504, 522, 524, 408]這些code,換個proxy試試
            proxy = random.choice(proxy_list) request.meta['proxy'] = proxy return self._retry(request, reason, spider) or response return response #RetryMiddleware類裏有個常量,記錄了鏈接超時那些異常
    #EXCEPTIONS_TO_RETRY = (defer.TimeoutError, TimeoutError, DNSLookupError,
    # ConnectionRefusedError, ConnectionDone, ConnectError,
    # ConnectionLost, TCPTimedOutError, ResponseFailed,
    # IOError, TunnelError)
    def process_exception(self, request, exception, spider): if isinstance(exception, self.EXCEPTIONS_TO_RETRY) and not request.meta.get('dont_retry', False): #這裏能夠寫出現異常那些你的處理 
            proxy = random.choice(proxy_list) request.meta['proxy'] = proxy time.sleep(random.randint(3, 5)) return self._retry(request, exception, spider) #_retry是RetryMiddleware中的一個私有方法,主要做用是
    #1.對request.meta中的retry_time進行+1 
    #2.將retry_times和max_retry_time進行比較,若是前者小於等於後者,利用copy方法在原來的request上覆制一個新request,並更新其retry_times,並將dont_filter設爲True來防止因url重複而被過濾。
    #3.記錄重試reason

 

 

spider中間件

spider中間件用於處理引擎傳回的response及spider生成的item和Request

主要做用:

  • 處理spider的異常
  • 對item在進入管道以前操做
  • 根據引擎傳入的響應,再進入回調函數前先處理

默認spider中間件

SPIDER_MIDDLEWARES_BASE

{ 'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50, 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500, 'scrapy.spidermiddlewares.referer.RefererMiddleware': 700, 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800, 'scrapy.spidermiddlewares.depth.DepthMiddleware': 900, }

同理,激活中間件

SPIDER_MIDDLEWARES = { 'myproject.middlewares.CustomSpiderMiddleware': 543, 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None, }

 

自定義spider中間件

1.process_spider_input(self, response, spider)

對於經過spider中間件並進入spider的每一個響應,都會調用此方法進行處理。

process_spider_input()應該返回None或提出異常。

  • 若是它返回None,Scrapy將繼續處理此響應,執行全部其餘中間件,直到最後,響應被交給spider進行處理。
  • 若是它引起異常,Scrapy將不會調用任何其餘spider中間件的process_spider_input(),將調用請求errback(若是有的話),不然它將進入process_spider_exception()鏈

參數:
  response(Responseobject) - 正在處理的響應
  spider(Spiderobject) - 此響應所針對的spider

 

2.process_spider_output(self, response, result, spider)

在處理完響應以後,使用Spider返回的結果調用此方法。

process_spider_output()必須返回一個可迭代的 Request,dict或Item 對象。

參數:
  response(Responseobject) - 從spider生成此輸出的響應
  result(可迭代的Request,dict或Item對象) - spider返回的結果
  spider(Spiderobject) - 正在處理其結果的spider

 

3.process_spider_exception(self, response, exception, spider)

當spider或process_spider_output() 方法(來自先前的spider中間件)引起異常時,將調用此方法。

process_spider_exception()應該返回一個None或一個可迭代的Request,dict或 Item對象。

  • 若是它返回None,Scrapy將繼續處理此異常,執行process_spider_exception()如下中間件組件中的任何其餘組件,直到沒有剩餘中間件組件而且異常到達引擎(它被記錄並丟棄)。
  • 若是它返回一個iterable,那麼process_spider_output()管道將從下一個spider中間件開始啓動,而且不會process_spider_exception()調用其餘任何一個 。

參數:
  response(Responseobject) - 引起異常時正在處理的響應
  exception(異常對象) - 引起異常
  spider(Spiderobject) - 引起異常的spider

 

4.process_start_requests(self, start_requests, spider)

當spider運行到start_requests()的時候,爬蟲中間件的process_start_requests()方法被調用

它接收一個iterable(在start_requests參數中)而且必須返回另外一個可迭代的Request對象。

參數:
start_requests(可迭代Request) - 開始請求
spider(Spiderobject) - 啓動請求所屬的spider

 

5.from_crawler(cls, crawler)

這個類方法一般是訪問settings和signals的入口函數

 

spider中間件總結

1.spider開始start_requests()的時候,spider中間件的process_start_requests()方法被調用

2.下載response成功後,返回到spider 回調函數parse前,調用process_spider_input()

3.當spider yield scrapy.Request()或者yield item的時候,spider中間件的process_spider_output()方法被調用。

4.當spider出現了Exception的時候,spider中間件的process_spider_exception()方法被調用。

 

中間件與spider的關係流程圖

 

相關文章
相關標籤/搜索