前言
MiddleWare,顧名思義,中間件。主要處理請求(例如添加代理IP、添加請求頭等)和處理響應
本篇文章主要講述下載器中間件的概念,以及如何使用中間件和自定義中間件。
MiddleWare分類
依舊是那張熟悉的架構圖。
從圖中看,中間件主要分為兩類:
- Downloader MiddleWare:下載器中間件
- Spider MiddleWare:Spider中間件
本篇文主要介紹下載器中間件,先看官方的定義:
下載器中間件是介於Scrapy的request/response處理的鈎子框架。 是用於全局修改Scrapy request和response的一個輕量、底層的系統。
作用
如架構圖中所描述的一樣,下載器中間件位於engine和下載器之間。engine將未處理的請求發送給下載器的時候,會經過下載器中間件,這時候在中間件里可以包裝請求,例如修改請求頭信息(設置UA、cookie等)和添加代理IP。
當下載器將網站的響應發送給engine的時候,也會經過下載器中間件,這里我們就可以對響應內容進行處理。
內置下載器中間件
Scrapy內置了很多下載器中間件供開發者使用。當我們啟動一個Scrapy爬蟲時,Scrapy會自動幫助我們啟用這些中間件。如圖:
圖中就是在啟動Scrapy程序時控制台打印的日志信息,我們發現Scrapy幫我們啟用了很多下載器中間件和Spider中間件。
這里,先看看這些內置的中間件是如何發揮作用的?
RetryMiddleware
其實,這些內置中間件是和settings中的配置配套使用的。這里就拿RetryMiddleware為例。它的作用主要是:當請求失敗時,可以根據RETRY_ENABLED和RETRY_TIMES配置來啟用重試策略以及決定重試次數。就醬!!
那么問題又來了,這么多中間件,我去哪里找這個settings配置和中間件的對應關系啊??
這里我的方法有兩種:
- 去官方文檔,上篇文章有鏈接
- 看源碼注釋,在scrapy包下的都有中間件對應的py文件
注釋里面寫的明明白白,代碼中獲取的參數也一覽無余。
自定義中間件
有時候,內置的中間件滿足不了自己的需求,所以我們就要自力更生,自定義中間件。所有的中間件都在middlewares.py中進行定義。
我們打開middlewares.py,發現里面已經自動生成了一個下載器中間件和Spider中間件。
先看自生成的下載器中間件模板:
可以看到里面主要有五個方法:
- from_crawler:類方法,用於初始化中間件
- process_request:每個request通過下載中間件時,都會調用該方法,對應架構圖步驟4
- process_response:處理下載器返回的響應內容,對應架構圖步驟7
- process_exception:當下載器或者處理請求異常時,調用此方法
- spider_opened:內置的信號量回調方法,這里先不關注,先不關注!
這里主要關注3,順帶了解一下4、5。
process_request()
此方法有兩個參數:
- request:spider發起的需要處理的request
- spider:該request對應的spider,暫定信號量細講這個對象
def process_request(self, request, spider):
# Called for each request that goes through the downloader middleware.
# Must either:
# - return None: continue processing this request
# - or return a Response object
# - or return a Request object
# - or raise IgnoreRequest: process_exception() methods of
# installed downloader middleware will be called
return None
這里主要是為了讓大家看注釋,看注釋的目的是為了告訴大家:此方法必須返回值。
- None:基本上用的都是這個返回值。表示這個請求可以進去下一個中間件進行處理了。
- request:停止調用process_request方法,並重新將request放回隊列重新調度
- response:不會調用其他的 process_request,直接返回response,執行process_response。
還有一個是raise拋出異常,其實基本上返回值都用None,其他的目前可以僅做了解,有興趣的可以自己探索一下。
process_response()
此方法有三個參數:
- request:response所對應的request
- response:被處理的response
- spider:response所對應的spider
def process_response(self, request, response, spider):
# Called with the response returned from the downloader.
# Must either;
# - return a Response object
# - return a Request object
# - or raise IgnoreRequest
return response
一樣是看注釋,返回值有兩個:
- response:下載器返回的響應內容,在各個中間件的process_response處理
- request:停止調用process_response方法,響應不會到達spider,並重新將request放回隊列重新調度
這里記住,只要return response就行。
process_exception()
def process_exception(self, request, exception, spider):
# Called when a download handler or a process_request()
# (from other downloader middleware) raises an exception.
# Must either:
# - return None: continue processing this exception
# - return a Response object: stops process_exception() chain
# - return a Request object: stops process_exception() chain
pass
此方法就是當上面兩個方法拋出異常的時候就會進入此方法,返回值有三個,意思和上面的差不多,用None就行。
啟用和禁用中間件
自定義的中間件,有時候會和內置中間件功能重復,也擔心功能上互相覆蓋。所以這里我們可以選擇,在配置中關掉內置中間件。
我個人比較喜歡自定義User-Agent中間件,但是Scrapy內置UserAgentMiddleware中間件,這就沖突了。如果內置中間件執行優先級低,后執行的話,則內置的UA就會覆蓋自定義的UA。所以,我們需要關掉這個內置中UA中間件。
DOWNLOADER_MIDDLEWARES參數用來設置下載器中間件。其中,Key為中間件路徑,Value為中間件執行優先級,數字越小,越先執行,當Value為None時,表示禁用。
# settings.py
DOWNLOADER_MIDDLEWARES = {
# 禁用默認的useragent插件
'scrapy.downloadermiddleware.useragent.UserAgentMiddleware': None,
# 啟用自定義的中間件
'ScrapyDemo.middlewares.VideospiderDownloaderMiddleware': 543,
}
這樣,內置的UA中間件則被禁用。
調用優先級
其次我們要明確的是:中間件是鏈式調用,一個請求會根據中間件的優先級,先后經過每個中間件,響應也是。
上面也說了,每個中間件都會設置一個執行優先級,數字越小越先執行。例如中間件1的優先級設置為200,中間件2的優先級設置為300。
當spider發起一個請求時,request會先經過中間件1的process_request進行處理,然后到達中間件2的此方法進行處理,當經過所有的中間件的此方法處理之后,最后到達下載器進行網站請求,然后返回響應內容。
process_response就是逆序處理,先到達中間件2的此方法,再到達中間件1,最后響應返回spider中,由開發者處理。
實踐
這里我們自定義一個下載器中間件,來添加User-Agent。
自定義中間件
在middlewares.py中定義一個中間件:
class CustomUserAgentMiddleWare(object):
def process_request(self, request, spider):
request.headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
return None
def process_response(self, request, response, spider):
print(request.headers['User-Agent'])
return response
啟用中間件
為了直觀,我們不修改settings.py全局配置,依舊使用代碼內局部配置。
import scrapy
class DouLuoDaLuSpider(scrapy.Spider):
name = 'DouLuoDaLu'
allowed_domains = ['v.qq.com']
start_urls = ['https://v.qq.com/detail/m/m441e3rjq9kwpsc.html']
custom_settings = {
'DOWNLOADER_MIDDLEWARES': {
# 禁用默認的useragent插件
'scrapy.downloadermiddleware.useragent.UserAgentMiddleware': None,
# 啟用自定義的中間件
'ScrapyDemo.middlewares.CustomUserAgentMiddleWare': 400
}
}
def parse(self, response):
pass
這里首先禁用了默認的UA中間件,然后啟用了自定義的UA中間件。並且我在最后一行打上斷點,Debug看UA是否設置成功。
測試結果
Debug模式啟動程序,這里先把自定義的UA中間件禁用。
如圖,request的UA是Scrapy。我們將注釋去掉,啟動UA中間件,再次啟動程序測試。
如圖,request的UA已經變成我在中間件中設置的UA了。
設置代理IP
依舊是在process_request方法中設置代理IP。
代碼如下:
request.meta["proxy"] = 'http://ip:port'
結語
下載器中間件主要的功能還是包裝請求,我個人自定義下載器中間件都是用來動態設置UA和實時檢測更換代理IP。至於其他的場景需求,內置的下載器中間件基本上夠用。
當然,不去學習下載器中間件這一塊的知識同樣可以開發Scrapy爬蟲,但是下載器中間件會讓你的爬蟲更加完美。
本來想把下載器中間件和Spider中間件寫在一篇中,但是知識點太碎,不好排版,而且還容易混淆,所以Spider中間件就留在下一篇寫,期待下一次相遇。
95后小程序員,寫的都是日常工作中的親身實踐,置身於初學者的角度從0寫到1,詳細且認真。文章會在公眾號 [入門到放棄之路] 首發,期待你的關注。