廢話不多說,兩個中間件,一個下載器,另一個Spider
-
下載器中間件
位於Scrapy引擎和下載器之間,主要用來處理從EGINE傳到DOWLOADER的請求request,已經從DOWNLOADER傳到EGINE的響應response,你可用該中間件做以下幾件事
官方英文文檔:process a request just before it is sent to the Downloader (i.e. right
before Scrapy sends the request to the website); change received
response before passing it to a spider; send a new Request instead of
passing received response to a spider; pass response to a spider
without fetching a web page; silently drop some requests.簡單點理解就是說,引擎engine將request對象交給下載器之前,會經過下載器中間件;此時,中間件提供了一個方法 process_request,可以對 request對象進行設置隨機請求頭、IP代理、Cookie等;當下載器完成下載時,獲得到 response對象,將它交給引擎engine的過程中,再一次經過 下載器中間件;此時,中間件提供了另一個方法 process_response;可以判斷 response對象的狀態碼,來決定是否將 response提交給引擎。
當然可以自己寫中間件,想要寫明白,就需要搞懂以下這三個方法。
- process_request(request, spider)
- process_response(request, response, spider)
- process_exception(request, exception, spider)
-
方法1:process_request(request,spider)
當每個request通過下載中間件時,該方法被調用。
process_request() 必須返回其中之一: 返回 None 、返回一個 Response 對象、返回一個 Request 對象或raise IgnoreRequest 。
重點在這:返回值很重要。
- 返回 None 時,,Scrapy將繼續處理該request,執行其他的中間件的相應方法,直到合適的下載器處理函數(download handler)被調用, 該request被執行(其response被下載)。
- 如果其返回 Response 對象,Scrapy將不會調用 任何 其他的 process_request() 或 process_exception() 方法,或相應地下載函數; 其將返回該response。 已安裝的中間件的 process_response() 方法則會在每個response返回時被調用。
- 如果其返回 Request 對象,Scrapy則停止調用 process_request方法並重新調度返回的request。當新返回的request被執行後, 相應地中間件鏈將會根據下載的response被調用。
- 如果其raise一個 IgnoreRequest 異常,則安裝的下載中間件的 process_exception() 方法會被調用。如果沒有任何一個方法處理該異常, 則request的errback(Request.errback)方法會被調用。如果沒有代碼處理拋出的異常, 則該異常被忽略且不記錄(不同於其他異常那樣)。
-
方法2 process_response(request, response, spider)
同樣的,返回值一樣很重要:
process_request() 必須返回以下之一: 返回一個 Response 對象、 返回一個 Request 對象或raise一個 IgnoreRequest 異常。
-
如果其返回一個 Response (可以與傳入的response相同,也可以是全新的對象), 該response會被在鏈中的其他中間件的 process_response() 方法處理。
-
如果其返回一個 Request 對象,則中間件鏈停止, 返回的request會被重新調度下載。處理類似於 process_request() 返回request所做的那樣。
-
如果其拋出一個 IgnoreRequest 異常,則調用request的errback(Request.errback)。 如果沒有代碼處理拋出的異常,則該異常被忽略且不記錄(不同於其他異常那樣)。
-
-
方法3 process_exception(request, exception, spider)
返回值
process_exception() 應該返回以下之一: 返回 None 、 一個 Response 對象、或者一個 Request 對象。
-
如果其返回 None ,Scrapy將會繼續處理該異常,接着調用已安裝的其他中間件的 process_exception() 方法,直到所有中間件都被調用完畢,則調用默認的異常處理。
-
如果其返回一個 Response 對象,則已安裝的中間件鏈的 process_response() 方法被調用。Scrapy將不會調用任何其他中間件的 process_exception() 方法。
-
如果其返回一個 Request 對象, 則返回的request將會被重新調用下載。這將停止中間件的 process_exception() 方法執行,就如返回一個response的那樣。
-
熟悉了這三個方法,基本上可以寫出,UA、Cookie、代理、Selenium中間件了。想要更進一步,那麼需要對 Scrapy內置的下載器中間件有所掌握。
內置的BASE 下載器中間件如下:
{
'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 400,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 500,
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 550,
'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.chunked.ChunkedTransferMiddleware': 830,
'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
}
按照優先級來介紹:
-
RobotsTxtMiddleware
該中間件過濾所有robots.txt eclusion standard中禁止的request。
確認該中間件及 ROBOTSTXT_OBEY 設置被啓用以確保Scrapy尊重robots.txt。
主要看你,settings文件中 ROBOTSTXT_OBEY = False 是否設置了 False
-
HttpAuthMiddleware
該中間件完成某些使用 Basic access authentication (或者叫HTTP認證)的spider生成的請求的認證過程。
-
DownloadTimeoutMiddleware
該中間件設置 DOWNLOAD_TIMEOUT 指定的request下載超時時間.
這個中間件,我用過。繼承它之後,重寫方法,實現對代理IP訪問慢,設置一個超時時間,對它進行判斷,超過改時間,進行更換proxy。
-
UserAgentMiddleware
用於覆蓋spider的默認user agent的中間件。
要使得spider能覆蓋默認的user agent,其 user_agent 屬性必須被設置。
經常使用之一,你可以選擇繼承該中間件,也可以自己寫一個。它們之間不是覆蓋的關係,而是合併。
-
RetryMiddleware
該中間件將重試可能由於臨時的問題,例如連接超時或者HTTP 500錯誤導致失敗的頁面。
爬取進程會收集失敗的頁面並在最後,spider爬取完所有正常(不失敗)的頁面後重新調度。 一旦沒有更多需要重試的失敗頁面,該中間件將會發送一個信號(retry_complete), 其他插件可以監聽該信號。 -
DefaultHeadersMiddleware
該中間件設置 DEFAULT_REQUEST_HEADERS 指定的默認request header。
-
MetaRefreshMiddleware
該中間件根據meta-refresh html標籤處理request重定向。
-
HttpCompressionMiddleware
該中間件提供了對壓縮(gzip, deflate)數據的支持
-
RedirectMiddleware
該中間件根據response的狀態處理重定向的request。通過該中間件的(被重定向的)request的url可以通過 Request.meta 的 redirect_urls 鍵找到。 -
CookiesMiddleware
參見博客 CookieMiddleWare詳解
-
HttpProxyMiddleware
該中間件提供了對request設置HTTP代理的支持。您可以通過在 Request 對象中設置 proxy 元數據來開啓代理。 -
ChunkedTransferMiddleware
該中間件添加了對 chunked transfer encoding 的支持。 -
DownloaderStats
pass
-
HttpCacheMiddleware
該中間件爲所有HTTP request及response提供了底層(low-level)緩存支持。 其由cache存儲後端及cache策略組成。
說了這麼多內置中間件,可以禁用某些中間件嗎? 當然可以
假如,想禁用 RetryMiddleware中間件,只需要在settings文件種的中間件字典里加入, ‘scrapy.downloadermiddlewares.retry.RetryMiddleware’: None,
說了這麼多,還不如一張圖來的清晰明瞭
-
Spider 中間件
Spider Middleware是介入到Scrapy的Spider處理機制的鉤子框架。如以下經典 scrapy流程圖:
Spider Middleware有如下三個作用:
- 我們可以在Downloader生成的Response發送給Spider之前,也就是在Response發送給Spider之前對Response進行處理。
- 我們可以在Spider生成的Request發送給Scheduler之前,也就是在Request發送給Scheduler之前對Request進行處理。
- 我們可以在Spider生成的Item發送給Item Pipeline之前,也就是在Item發送給Item Pipeline之前對Item進行處理。
-
同樣的,想要寫好 Spider中間件,需要掌握以下四個方法
-
process_spider_input(response, spider)
-
process_spider_output(response, result, spider)
-
process_spider_exception(response, exception, spider)
-
process_start_requests(start_requests, spider)
具體如何使用請看----》》》 spider中間件使用
-
說實話,Spider Middleware使用的頻率不高,使用的絕大部分都是下載器中間件。這裏,我再舉幾個自定義下載器中間件的例子。
1. 設置隨機UA
一般有兩種方法,一是繼承 scrapy提供的內置 UserAgentMiddleware
使用 fake_useragent 庫 可以隨機產生一個UA,不錯的一個庫。最後,記得在settings配置文件中添加該中間件
法1:
# author:dayin
# Date:2019/12/17 0017
# UserAgentMiddleWare.py
from fake_useragent import UserAgent
class UserAgentMiddleWare(object):
ua = UserAgent()
def process_request(self, request, spider):
request.meta['User-Agent'] = self.ua.random
法2:
class UserAgentMiddleWare2(UserAgentMiddleware):
ua = UserAgent()
def process_request(self, request, spider):
request.meta['User-Agent'] = self.ua.random
2. 設置IP代理
# author:dayin
# Date:2019/12/17 0017
import json
import requests
class ProxyMiddleWare(object):
def process_request(self, request, spider):
request.meta['proxy'] = self.getIP()
print('當前的代理ip爲....', request.meta['proxy'])
@staticmethod
def getIP():
ip_port = json.loads(requests.get('http://127.0.0.1:5555/get').json())['proxy']
return 'http://' + ip_port
def process_exception(self, request, spider, exception):
print('----' * 100)
print('代理失效了,換一個代理IP')
request.meta['proxy'] = self.getIP()
print('----' * 100)
return request.replace(dont_filter=True)
我在本地端口5555設置了一個IP代理池,訪問它可以返回一個代理IP
end…