若是有學過django的同窗,應該對這個名詞不陌生了,在django中,中間件能夠對請求作統一批量的處理python
那麼在爬蟲中,中間件的做用也是作批量處理的,好比把全部請求的請求頭添加一個值等等等。django
因爲爬蟲是一個發請求,獲取響應的過程,因此在scrapy框架中有兩個中間件。json
在scrapy框架中所謂的中間件本質上就是一個類,裏面有一些方法。cookie
固然你想應用中間件,必需要在settings文件中註冊一下,優先級仍然是數值小的優先級高,通常放在上面。框架
# 爬蟲中間件 SPIDER_MIDDLEWARES = { 'spider1.middlewares.Spider1SpiderMiddleware': 543, } # 下載中間件 DOWNLOADER_MIDDLEWARES = { 'spider1.middlewares.Spider1DownloaderMiddleware': 543, }
實際上中間件的方法只有四個就好了,可是框架給咱們綁定了一個爬蟲打開時的信號。scrapy
from scrapy import signals class Spider1SpiderMiddleware: @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() # 建立spider(爬蟲對象)的時候,註冊一個信號 # 信號: 當爬蟲的打開的時候 執行 spider_opened 這個方法 crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_spider_input(self, response, spider): # 下載完成後,執行,而後交給parse處理 return None def process_spider_output(self, response, result, spider): """ 經歷過parse函數以後執行 :param response: 上一次請求返回的結果 :param result: yield的對象 包含 [item/Request] 對象的可迭代對象 :param spider: 當前爬蟲對象 :return: 返回Request對象 或 Item對象 """ for i in result: yield i def process_spider_exception(self, response, exception, spider): """若是執行parse拋出異常的話 會執行這個函數 默認不對異常處理交給下一個中間件處理""" pass def process_start_requests(self, start_requests, spider): """ 爬蟲啓動時調用 :param start_requests: 包含 Request 對象的可迭代對象 :param spider: :return: Request 對象 """ for r in start_requests: yield r def spider_opened(self, spider): # 生成爬蟲日誌 spider.logger.info('Spider opened: %s' % spider.name)
爬蟲開始時有個起始url,這個時候會通過爬蟲中間件的 process_start_requests 方法,而後去下載,接着ide
咱們看到 process_spider_input 和 process_spider_output 這兩個方法中參數都有一個response參數能夠判斷出函數
這兩個方法都是在下載完成後,拿到response以後纔會執行的。網站
當下載完成後再通過爬蟲中間件,這個時候會執行 process_spider_input 方法,這個時候的response中有url
包含此次請求的全部內容,其中請求的request對象也被封裝成了 response.resquest 。
而後來到 spider 文件中,經歷parse方法,這個時候返回值多是Request對象或者item對象,而後若是
parse 方法中拋出異常會執行爬蟲中間件的 process_spider_exception 方法,通常狀況下無異常,繼續走爬蟲
中間件中的 process_spider_output 方法。這就是爬蟲中間件中方法的在什麼地方執行的過程。
至於沒講到的那兩個方法,是基於信號作出的關於爬蟲開啓時日誌的拓展。對爬蟲中間件無影響,後面我會將
到關於信號實現的自定義拓展。
默認下載中間件源碼:
class Spider1DownloaderMiddleware: @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. # 這個方法同上,和爬蟲中間件同樣的功能 s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_request(self, request, spider): """ 請求須要被下載時,通過全部下載中間件的process_request調用 spider處理完成,返回時調用 :param request: :param spider: :return: None,繼續往下執行,去下載 Response對象,中止process_request的執行,開始執行process_response Request對象,中止中間件的執行,將Request從新放到調度器中 raise IgnoreRequest異常,中止process_request的執行,開始執行process_exception """ return None def process_response(self, request, response, spider): """ 下載獲得響應後,執行 :param request: 請求對象 :param response: 響應對象 :param spider: 爬蟲對象 :return: 返回request對象,中止中間件,將Request對象從新放到調度器中 返回response對象,轉交給其餘中間件process_response raise IgnoreRequest 異常: 調用Request.errback """ return response def process_exception(self, request, exception, spider): """當下載處理器(download handler)或process_request() (下載中間件)拋出異常 :return None: 繼續交給後續中間件處理異常 Response對象: 中止後續process_exception方法 Request對象: 中止中間件,request將會被從新調用下載 """ pass def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
process_request(self, request, spider)
該方法是下載器中間件類中的一個方法,該方法是每一個請求從引擎發送給下載器下載以前都會通過該方法。因此該
方法常常用來處理請求頭的替換,IP的更改,Cookie等的替換。
參數:
request (Request 對象) – 處理的request
spider (Spider 對象) – 該request對應的spider
返回值:
返回 None:Scrapy將繼續處理該request,執行其餘的中間件的相應方法,直到合適的下載器處理函數
(download handler)被調用,該request被執行(其response被下載)。
若是其返回Response對象:Scrapy將不會調用任何其餘的 process_request ( ) 或 process_exception ( )
方法,或相應地下載函數; 其將返回response。
若是其返回Request對象,Scrapy則中止調用 process_request 方法並從新調度返回的request。當新返回的
request被執行後, 相應地中間件鏈將會根據下載的response被調用。
若是其raise一個 IgnoreRequest 異常,則安裝的下載中間件的 process_exception ( ) 方法會被調用。如
果沒有任何一個方法處理該異常, 則request的 errback(Request.errback) 方法會被調用。若是沒有代碼處
理拋出的異常, 則該異常被忽略且不記錄(不一樣於其餘異常那樣)。
process_response(self, request, response, spider)
該方法是下載器中間件類中的一個方法,該方法是每一個響應從下載器發送給spider以前都會通過該方法。
參數:
request (Request 對象) – response所對應的request
response (Response 對象) – 被處理的response
spider (Spider 對象) – response所對應的spider
返回值:
若是其返回一個Response (能夠與傳入的response相同,也能夠是全新的對象), 該response會被其餘中間件的
process_response() 方法處理。
若是其返回一個Request對象,則返回的request會被從新調度下載。處理相似於 process_request() 返回
request所作的那樣。
若是其拋出一個 IgnoreRequest 異常,則調用request的 errback(Request.errback) 。若是沒有代碼處理
拋出的異常,則該異常被忽略且不記錄(不一樣於其餘異常那樣)。
通常門戶網站都會在請求頭中的User-Agent中設置反爬,因此咱們常常會在寫爬蟲的時候,要手動處理
User-Agent,可是通常狀況下咱們都是大批量爬取,若是在這裏指定一個 User-Agent 的話,也會可能被
網站檢測出來是爬蟲,這裏咱們提供一種隨機 User-Agent 的方法。
import Faker class UserAgentDownloaderMiddleware(object): def __init__(self): self.fake = faker.Faker() def process_request(self, request, spider): # 隨機生成一個User-Agent useragent = self.fake.user_agent() # 更新請求頭的內容 request.headers.update({"User-Agent": useragent}) return None
有時候咱們想要爬取的數據能夠須要登陸才能看到,這時候咱們就須要登陸網頁。登錄後的網頁通常都會在本地保存
該網頁的登陸信息Cookies在本地。只要獲取該Cookie,那麼在之後跳轉到其餘網頁的時候,只須要攜帶該Cookie便可。
class UserAgentDownloaderMiddleware(object): def __init__(self): self.fake = faker.Faker() def process_request(self, request, spider): # 隨機生成一個User-Agent useragent = self.fake.user_agent() # 更新請求頭的內容 request.headers.update({"useragent": useragent}) return None
工做中你用爬蟲,必定最經常使用的就是代理,這裏咱們能夠自定義代理中間件
def get_proxy(): """獲取代理的函數""" response = requests.get('http://134.175.188.27:5010/get/') data = response.json() return data["proxy"] class ProxyDownloaderMiddleware(object): """代理中間件""" def process_request(self, request, spider): request.meta['proxy'] = get_proxy() return None
設置了代理以後,可能會出現代理不可用,通常咱們都是在配置文件中設置重試。
RETRY_ENABLED = True # 是否開啓超時重試 RETRY_TIMES = 2 # initial response + 2 retries = 3 requests 重試次數 RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429] # 重試的狀態碼 DOWNLOAD_TIMEOUT = 1 # 1秒沒有請求到數據,主動放棄