第44讲:scrapy中间键Middleware的使用
我們在 Scrapy 架構(gòu)中,可以看到有一個叫作 Middleware 的概念,中文翻譯過來就叫作中間件,在 Scrapy 中有兩種 Middleware,一種是 Spider Middleware,另一種是 Downloader Middleware,本節(jié)課我們分別來介紹下。
1.Spider Middleware 的用法
Spider Middleware 是介入 Scrapy 的 Spider 處理機制的鉤子框架。
當 Downloader 生成 Response 之后,Response 會被發(fā)送給 Spider,在發(fā)送給 Spider 之前,Response 會首先經(jīng)過 Spider Middleware 處理,當 Spider 處理生成 Item 和 Request 之后,Item 和 Request 還會經(jīng)過 Spider Middleware 的處理。
Spider Middleware 有如下三個作用。
-
我們可以在 Downloader 生成的 Response 發(fā)送給 Spider 之前,也就是在 Response 發(fā)送給 Spider 之前對 Response 進行處理。
-
我們可以在 Spider 生成的 Request 發(fā)送給 Scheduler 之前,也就是在 Request 發(fā)送給 Scheduler 之前對 Request 進行處理。
-
我們可以在 Spider 生成的 Item 發(fā)送給 Item Pipeline 之前,也就是在 Item 發(fā)送給 Item Pipeline 之前對 Item 進行處理。
使用說明
需要說明的是,Scrapy 其實已經(jīng)提供了許多 Spider Middleware,它們被 SPIDER_MIDDLEWARES_BASE 這個變量所定義。
SPIDER_MIDDLEWARES_BASE 變量的內(nèi)容如下:
{'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, }和 Downloader Middleware 一樣,Spider Middleware 首先加入 SPIDER_MIDDLEWARES 的設(shè)置中,該設(shè)置會和 Scrapy 中 SPIDER_MIDDLEWARES_BASE 定義的 Spider Middleware 合并。然后根據(jù)鍵值的數(shù)字優(yōu)先級排序,得到一個有序列表。第一個 Middleware 是最靠近引擎的,最后一個 Middleware 是最靠近 Spider 的。
1.1 核心方法
Scrapy 內(nèi)置的 Spider Middleware 為 Scrapy 提供了基礎(chǔ)的功能。如果我們想要擴展其功能,只需要實現(xiàn)某幾個方法即可。
每個 Spider Middleware 都定義了以下一個或多個方法的類,核心方法有如下 4 個。
-
process_spider_input(response, spider)
-
process_spider_output(response, result, spider)
-
process_spider_exception(response, exception, spider)
-
process_start_requests(start_requests, spider)
只需要實現(xiàn)其中一個方法就可以定義一個 Spider Middleware。下面我們來看看這 4 個方法的詳細用法。
1.1.1 process_spider_input(response, spider)
當 Response 通過 Spider Middleware 時,該方法被調(diào)用,處理該 Response。
方法的參數(shù)有兩個:
-
response,即 Response 對象,即被處理的 Response;
-
spider,即 Spider 對象,即該 response 對應(yīng)的 Spider。
process_spider_input() 應(yīng)該返回 None 或者拋出一個異常。
-
如果其返回 None,Scrapy 將會繼續(xù)處理該 Response,調(diào)用所有其他的 Spider Middleware 直到 Spider 處理該 Response。
-
如果其拋出一個異常,Scrapy 將不會調(diào)用任何其他 Spider Middleware 的 process_spider_input() 方法,并調(diào)用 Request 的 errback() 方法。 errback 的輸出將會以另一個方向被重新輸入到中間件中,使用 process_spider_output() 方法來處理,當其拋出異常時則調(diào)用 process_spider_exception() 來處理。
1.1.2 process_spider_output(response, result, spider)
當 Spider 處理 Response 返回結(jié)果時,該方法被調(diào)用。
方法的參數(shù)有三個:
-
response,即 Response 對象,即生成該輸出的 Response;
-
result,包含 Request 或 Item 對象的可迭代對象,即 Spider 返回的結(jié)果;
-
spider,即 Spider 對象,即其結(jié)果對應(yīng)的 Spider。
process_spider_output() 必須返回包含 Request 或 Item 對象的可迭代對象。
1.1.3 process_spider_exception(response, exception, spider)
當 Spider 或 Spider Middleware 的 process_spider_input() 方法拋出異常時, 該方法被調(diào)用。
方法的參數(shù)有三個:
-
response,即 Response 對象,即異常被拋出時被處理的 Response;
-
exception,即 Exception 對象,被拋出的異常;
-
spider,即 Spider 對象,即拋出該異常的 Spider。
process_spider_exception() 必須返回結(jié)果,要么返回 None , 要么返回一個包含 Response 或 Item 對象的可迭代對象。
-
如果其返回 None ,Scrapy 將繼續(xù)處理該異常,調(diào)用其他 Spider Middleware 中的 process_spider_exception() 方法,直到所有 Spider Middleware 都被調(diào)用。
-
如果其返回一個可迭代對象,則其他 Spider Middleware 的 process_spider_output() 方法被調(diào)用, 其他的 process_spider_exception() 將不會被調(diào)用。
1.1.4 process_start_requests(start_requests, spider)
該方法以 Spider 啟動的 Request 為參數(shù)被調(diào)用,執(zhí)行的過程類似于 process_spider_output() ,只不過其沒有相關(guān)聯(lián)的 Response 并且必須返回 Request。
方法的參數(shù)有兩個:
-
start_requests,即包含 Request 的可迭代對象,即 Start Requests;
-
spider,即 Spider 對象,即 Start Requests 所屬的 Spider。
其必須返回另一個包含 Request 對象的可迭代對象。
2.Downloader Middleware 的用法
Downloader Middleware 即下載中間件,它是處于 Scrapy 的 Request 和 Response 之間的處理模塊。
Scheduler 從隊列中拿出一個 Request 發(fā)送給 Downloader 執(zhí)行下載,這個過程會經(jīng)過 Downloader Middleware 的處理。另外,當 Downloader 將 Request 下載完成得到 Response 返回給 Spider 時會再次經(jīng)過 Downloader Middleware 處理。
也就是說,Downloader Middleware 在整個架構(gòu)中起作用的位置是以下兩個。
-
在 Scheduler 調(diào)度出隊列的 Request 發(fā)送給 Downloader 下載之前,也就是我們可以在 Request 執(zhí)行下載之前對其進行修改。
-
在下載后生成的 Response 發(fā)送給 Spider 之前,也就是我們可以在生成 Resposne 被 Spider 解析之前對其進行修改。
Downloader Middleware 的功能十分強大,修改 User-Agent、處理重定向、設(shè)置代理、失敗重試、設(shè)置 Cookies 等功能都需要借助它來實現(xiàn)。下面我們來了解一下 Downloader Middleware 的詳細用法。
2.1使用說明
需要說明的是,Scrapy 其實已經(jīng)提供了許多 Downloader Middleware,比如負責失敗重試、自動重定向等功能的 Middleware,它們被 DOWNLOADER_MIDDLEWARES_BASE 變量所定義。
DOWNLOADER_MIDDLEWARES_BASE 變量的內(nèi)容如下所示:
{'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 內(nèi)置的 Downloader Middleware 的名稱,鍵值代表了調(diào)用的優(yōu)先級,優(yōu)先級是一個數(shù)字,數(shù)字越小代表越靠近 Scrapy 引擎,數(shù)字越大代表越靠近 Downloader。每個 Downloader Middleware 都可以定義 process_request() 和 request_response() 方法來分別處理請求和響應(yīng),對于 process_request() 方法來說,優(yōu)先級數(shù)字越小越先被調(diào)用,對于 process_response() 方法來說,優(yōu)先級數(shù)字越大越先被調(diào)用。
如果自己定義的 Downloader Middleware 要添加到項目里,DOWNLOADER_MIDDLEWARES_BASE 變量不能直接修改。Scrapy 提供了另外一個設(shè)置變量 DOWNLOADER_MIDDLEWARES,我們直接修改這個變量就可以添加自己定義的 Downloader Middleware,以及禁用 DOWNLOADER_MIDDLEWARES_BASE 里面定義的 Downloader Middleware。下面我們具體來看看 Downloader Middleware 的使用方法。
2.2核心方法
Scrapy 內(nèi)置的 Downloader Middleware 為 Scrapy 提供了基礎(chǔ)的功能,但在項目實戰(zhàn)中我們往往需要單獨定義 Downloader Middleware。不用擔心,這個過程非常簡單,我們只需要實現(xiàn)某幾個方法即可。
每個 Downloader Middleware 都定義了一個或多個方法的類,核心的方法有如下三個。
-
process_request(request, spider)
-
process_response(request, response, spider)
-
process_exception(request, exception, spider)
我們只需要實現(xiàn)至少一個方法,就可以定義一個 Downloader Middleware。下面我們來看看這三個方法的詳細用法。
2.2.1 process_request(request, spider)
Request 被 Scrapy 引擎調(diào)度給 Downloader 之前,process_request() 方法就會被調(diào)用,也就是在 Request 從隊列里調(diào)度出來到 Downloader 下載執(zhí)行之前,我們都可以用 process_request() 方法對 Request 進行處理。方法的返回值必須為 None、Response 對象、Request 對象之一,或者拋出 IgnoreRequest 異常。
process_request() 方法的參數(shù)有如下兩個。
-
request,即 Request 對象,即被處理的 Request;
-
spider,即 Spider 對象,即此 Request 對應(yīng)的 Spider。
返回類型不同,產(chǎn)生的效果也不同。下面歸納一下不同的返回情況。
-
當返回為 None 時,Scrapy 將繼續(xù)處理該 Request,接著執(zhí)行其他 Downloader Middleware 的 process_request() 方法,直到 Downloader 把 Request 執(zhí)行后得到 Response 才結(jié)束。這個過程其實就是修改 Request 的過程,不同的 Downloader Middleware 按照設(shè)置的優(yōu)先級順序依次對 Request 進行修改,最后推送至 Downloader 執(zhí)行。
-
當返回為 Response 對象時,更低優(yōu)先級的 Downloader Middleware 的 process_request() 和 process_exception() 方法就不會被繼續(xù)調(diào)用,每個 Downloader Middleware 的 process_response() 方法轉(zhuǎn)而被依次調(diào)用。調(diào)用完畢之后,直接將 Response 對象發(fā)送給 Spider 來處理。
-
當返回為 Request 對象時,更低優(yōu)先級的 Downloader Middleware 的 process_request() 方法會停止執(zhí)行。這個 Request 會重新放到調(diào)度隊列里,其實它就是一個全新的 Request,等待被調(diào)度。如果被 Scheduler 調(diào)度了,那么所有的 Downloader Middleware 的 process_request() 方法會被重新按照順序執(zhí)行。
-
如果 IgnoreRequest 異常拋出,則所有的 Downloader Middleware 的 process_exception() 方法會依次執(zhí)行。如果沒有一個方法處理這個異常,那么 Request 的 errorback() 方法就會回調(diào)。如果該異常還沒有被處理,那么它便會被忽略。
2.2.2 process_response(request, response, spider)
Downloader 執(zhí)行 Request 下載之后,會得到對應(yīng)的 Response。Scrapy 引擎便會將 Response 發(fā)送給 Spider 進行解析。在發(fā)送之前,我們都可以用 process_response() 方法來對 Response 進行處理。方法的返回值必須為 Request 對象、Response 對象之一,或者拋出 IgnoreRequest 異常。
process_response() 方法的參數(shù)有如下三個。
-
request,是 Request 對象,即此 Response 對應(yīng)的 Request。
-
response,是 Response 對象,即此被處理的 Response。
-
spider,是 Spider 對象,即此 Response 對應(yīng)的 Spider。
下面對不同的返回情況做一下歸納:
-
當返回為 Request 對象時,更低優(yōu)先級的 Downloader Middleware 的 process_response() 方法不會繼續(xù)調(diào)用。該 Request 對象會重新放到調(diào)度隊列里等待被調(diào)度,它相當于一個全新的 Request。然后,該 Request 會被 process_request() 方法順次處理。
-
當返回為 Response 對象時,更低優(yōu)先級的 Downloader Middleware 的 process_response() 方法會繼續(xù)調(diào)用,繼續(xù)對該 Response 對象進行處理。
-
如果 IgnoreRequest 異常拋出,則 Request 的 errorback() 方法會回調(diào)。如果該異常還沒有被處理,那么它便會被忽略。
2.2.3 process_exception(request, exception, spider)
當 Downloader 或 process_request() 方法拋出異常時,例如拋出 IgnoreRequest 異常,process_exception() 方法就會被調(diào)用。方法的返回值必須為 None、Response 對象、Request 對象之一。
process_exception() 方法的參數(shù)有如下三個。
-
request,即 Request 對象,即產(chǎn)生異常的 Request。
-
exception,即 Exception 對象,即拋出的異常。
-
spdier,即 Spider 對象,即 Request 對應(yīng)的 Spider。
下面歸納一下不同的返回值。
-
當返回為 None 時,更低優(yōu)先級的 Downloader Middleware 的 process_exception() 會被繼續(xù)順次調(diào)用,直到所有的方法都被調(diào)度完畢。
-
當返回為 Response 對象時,更低優(yōu)先級的 Downloader Middleware 的 process_exception() 方法不再被繼續(xù)調(diào)用,每個 Downloader Middleware 的 process_response() 方法轉(zhuǎn)而被依次調(diào)用。
-
當返回為 Request 對象時,更低優(yōu)先級的 Downloader Middleware 的 process_exception() 也不再被繼續(xù)調(diào)用,該 Request 對象會重新放到調(diào)度隊列里面等待被調(diào)度,它相當于一個全新的 Request。然后,該 Request 又會被 process_request() 方法順次處理。
以上內(nèi)容便是這三個方法的詳細使用邏輯。在使用它們之前,請先對這三個方法的返回值的處理情況有一個清晰的認識。在自定義 Downloader Middleware 的時候,也一定要注意每個方法的返回類型。
下面我們用一個案例實戰(zhàn)來加深一下對 Downloader Middleware 用法的理解。
3.項目實戰(zhàn)
新建一個項目,命令如下所示:
scrapy startproject scrapydownloadertest新建了一個 Scrapy 項目,名為 scrapydownloadertest。進入項目,新建一個 Spider,命令如下所示:
scrapy genspider httpbin httpbin.org新建了一個 Spider,名為 httpbin,源代碼如下所示:
import scrapy class HttpbinSpider(scrapy.Spider):name = 'httpbin'allowed_domains = ['httpbin.org']start_urls = ['http://httpbin.org/']def parse(self, response):pass接下來我們修改 start_urls 為:[‘http://httpbin.org/’]。隨后將 parse() 方法添加一行日志輸出,將 response 變量的 text 屬性輸出,這樣我們便可以看到 Scrapy 發(fā)送的 Request 信息了。
修改 Spider 內(nèi)容如下所示:
接下來運行此 Spider,執(zhí)行如下命令:
scrapy crawl httpbinScrapy 運行結(jié)果包含 Scrapy 發(fā)送的 Request 信息,內(nèi)容如下所示:
{"args": {}, "headers": {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Encoding": "gzip,deflate,br", "Accept-Language": "en", "Connection": "close", "Host": "httpbin.org", "User-Agent": "Scrapy/1.4.0 (+http://scrapy.org)"}, "origin": "60.207.237.85", "url": "http://httpbin.org/get" }我們觀察一下 Headers,Scrapy 發(fā)送的 Request 使用的 User-Agent 是 Scrapy/1.4.0(+http://scrapy.org),這其實是由 Scrapy 內(nèi)置的 UserAgentMiddleware 設(shè)置的,UserAgentMiddleware 的源碼如下所示:
from scrapy import signalsclass UserAgentMiddleware(object):def __init__(self, user_agent='Scrapy'):self.user_agent = user_agent@classmethoddef from_crawler(cls, crawler):o = cls(crawler.settings['USER_AGENT'])crawler.signals.connect(o.spider_opened, signal=signals.spider_opened)return odef spider_opened(self, spider):self.user_agent = getattr(spider, 'user_agent', self.user_agent)def process_request(self, request, spider):if self.user_agent:request.headers.setdefault(b'User-Agent', self.user_agent)在 from_crawler() 方法中,首先嘗試獲取 settings 里面的 USER_AGENT,然后把 USER_AGENT 傳遞給 init() 方法進行初始化,其參數(shù)就是 user_agent。如果沒有傳遞 USER_AGENT 參數(shù)就默認設(shè)置為 Scrapy 字符串。我們新建的項目沒有設(shè)置 USER_AGENT,所以這里的 user_agent 變量就是 Scrapy。接下來,在 process_request() 方法中,將 user-agent 變量設(shè)置為 headers 變量的一個屬性,這樣就成功設(shè)置了 User-Agent。因此,User-Agent 就是通過此 Downloader Middleware 的 process_request() 方法設(shè)置的。
修改請求時的 User-Agent 可以有兩種方式:一是修改 settings 里面的 USER_AGENT 變量;二是通過 Downloader Middleware 的 process_request() 方法來修改。
第一種方法非常簡單,我們只需要在 setting.py 里面加一行 USER_AGENT 的定義即可:
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'一般推薦使用此方法來設(shè)置。但是如果想設(shè)置得更靈活,比如設(shè)置隨機的 User-Agent,那就需要借助 Downloader Middleware 了。所以接下來我們用 Downloader Middleware 實現(xiàn)一個隨機 User-Agent 的設(shè)置。
在 middlewares.py 里面添加一個 RandomUserAgentMiddleware 的類,如下所示:
import randomclass RandomUserAgentMiddleware():def __init__(self):self.user_agents = ['Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)','Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2','Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1']def process_request(self, request, spider):request.headers['User-Agent'] = random.choice(self.user_agents)我們首先在類的 init() 方法中定義了三個不同的 User-Agent,并用一個列表來表示。接下來實現(xiàn)了 process_request() 方法,它有一個參數(shù) request,我們直接修改 request 的屬性即可。在這里我們直接設(shè)置了 request 對象的 headers 屬性的 User-Agent,設(shè)置內(nèi)容是隨機選擇的 User-Agent,這樣一個 Downloader Middleware 就寫好了。
不過,要使之生效我們還需要再去調(diào)用這個 Downloader Middleware。在 settings.py 中,將 DOWNLOADER_MIDDLEWARES 取消注釋,并設(shè)置成如下內(nèi)容:
DOWNLOADER_MIDDLEWARES = {'scrapydownloadertest.middlewares.RandomUserAgentMiddleware': 543,}接下來我們重新運行 Spider,就可以看到 User-Agent 被成功修改為列表中所定義的隨機的一個 User-Agent 了:
{"args": {}, "headers": {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Encoding": "gzip,deflate,br", "Accept-Language": "en", "Connection": "close", "Host": "httpbin.org", "User-Agent": "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)"}, "origin": "60.207.237.85", "url": "http://httpbin.org/get" }我們就通過實現(xiàn) Downloader Middleware 并利用 process_request() 方法成功設(shè)置了隨機的 User-Agent。
另外,Downloader Middleware 還有 process_response() 方法。Downloader 對 Request 執(zhí)行下載之后會得到 Response,隨后 Scrapy 引擎會將 Response 發(fā)送回 Spider 進行處理。但是在 Response 被發(fā)送給 Spider 之前,我們同樣可以使用 process_response() 方法對 Response 進行處理。比如這里修改一下 Response 的狀態(tài)碼,在 RandomUserAgentMiddleware 添加如下代碼:
def process_response(self, request, response, spider):response.status = 201return response我們將 response 對象的 status 屬性修改為 201,隨后將 response 返回,這個被修改后的 Response 就會被發(fā)送到 Spider。
我們再在 Spider 里面輸出修改后的狀態(tài)碼,在 parse() 方法中添加如下的輸出語句:
self.logger.debug('Status Code: ' + str(response.status))重新運行之后,控制臺輸出了如下內(nèi)容:
[httpbin] DEBUG: Status Code: 201可以發(fā)現(xiàn),Response 的狀態(tài)碼成功修改了。因此要想對 Response 進行處理,就可以借助于 process_response() 方法。
另外還有一個 process_exception() 方法,它是用來處理異常的方法。如果需要異常處理的話,我們可以調(diào)用此方法。不過這個方法的使用頻率相對低一些,在此不用實例演示。
4.本節(jié)代碼
本節(jié)源代碼為:
https://github.com/Python3WebSpider/ScrapyDownloaderTest
5. 結(jié)語
本節(jié)講解了 Spider Middleware 和 Downloader Middleware 的基本用法。利用它們我們可以方便地實現(xiàn)爬蟲邏輯的靈活處理,需要好好掌握。
總結(jié)
以上是生活随笔為你收集整理的第44讲:scrapy中间键Middleware的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL中B-tree索引和Hash索
- 下一篇: 第45讲:哪都能存,Item Pipel