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


 

概述

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

 

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

 

下載中間件

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

主要作用:

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

默認下載中間件

scrapy內置了一些默認配置,這些是不允許被修改的,通常是_BASE結尾的設置,比如DOWNLOADER_MIDDLEWARES_BASE下載中間件的默認設置,如下

{
    '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,而是繼續串聯起來

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文件,里面自動生成了兩個中間件示例或者說模板。我們如果要自定義中間件的話,可以在給的示例上修改,或者新建類實現方法,或者繼承已有的中間件重寫方法

以下是下載中間件可以實現的方法,在自定義中間件時,可以根據需求實現

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的關系流程圖

 

 


免責聲明!

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



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