Scrapy 下载器 中间件(Downloader Middleware)
?
Scrapy 下載器中間件官方文檔:https://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/downloader-middleware.html
官方 英文 文檔:http://doc.scrapy.org/en/latest/topics/downloader-middleware.html#topics-downloader-middleware
?
Scrapy 擴展中間件: 針對特定響應狀態碼,使用代理重新請求:https://www.cnblogs.com/my8100/p/scrapy_middleware_autoproxy.html
https://www.baidu.com/s?wd=中間件狀態碼不等于200重新請求
?
?
?
下載器中間件(Downloader Middleware)
?
下載器中間件是介于Scrapy的request/response處理的鉤子框架。 是用于全局修改Scrapy request和response的一個輕量、底層的系統。
用容易理解的話表述就是:更換代理IP,更換Cookies,更換User-Agent,自動重試 等。
?
1. 如果完全沒有中間件,爬蟲的流程如下圖所示。
2. 使用了中間件以后,爬蟲的流程如下圖所示。
?
?
激活下載器中間件
?
要激活下載器中間件組件,將其加入到 DOWNLOADER_MIDDLEWARES 設置中。 該設置是一個字典(dict),鍵為中間件類的路徑,值為其中間件的順序(order)。
這里是一個例子:
DOWNLOADER_MIDDLEWARES = {'myproject.middlewares.CustomDownloaderMiddleware': 543, }DOWNLOADER_MIDDLEWARES 設置會與 Scrapy 定義的 DOWNLOADER_MIDDLEWARES_BASE 設置合并(但不是覆蓋), 而后根據順序(order)進行排序,最后得到啟用中間件的有序列表: 第一個中間件是最靠近引擎的,最后一個中間件是最靠近下載器的。
關于如何分配中間件的順序請查看 DOWNLOADER_MIDDLEWARES_BASE 設置,而后根據您想要放置中間件的位置選擇一個值。 由于每個中間件執行不同的動作,您的中間件可能會依賴于之前(或者之后)執行的中間件,因此順序是很重要的。
如果您想禁止內置的(在 DOWNLOADER_MIDDLEWARES_BASE 中設置并默認啟用的)中間件, 您必須在項目的 DOWNLOADER_MIDDLEWARES 設置中定義該中間件,并將其值賦為 None 。 例如,如果您想要關閉user-agent中間件:
DOWNLOADER_MIDDLEWARES = {'myproject.middlewares.CustomDownloaderMiddleware': 543,'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, }最后,請注意,有些中間件需要通過特定的設置來啟用。更多內容請查看相關中間件文檔。
?
?
編寫您自己的下載器中間件
?
編寫下載器中間件十分簡單。每個中間件組件是一個定義了以下一個或多個方法的Python類:
class scrapy.downloadermiddlewares.DownloaderMiddleware
?
?
process_request(request, spider):
?
當每個 request 通過下載中間件時,該方法被調用。
參數:
- request (Request 對象) – 處理的request
- spider (Spider 對象) – 該request對應的spider
返回值:process_request() 必須返回其中之一:
- 返回 None?。如果其返回 None ,Scrapy將繼續處理該request,執行其他的中間件的相應方法,直到合適的下載器處理函數(download handler)被調用, 該request被執行(其response被下載)。
- 返回一個 Response 對象。如果其返回 Response 對象,Scrapy將不會調用 任何 其他的 process_request() 或 process_exception() 方法,或相應地下載函數; 其將返回該response。 已安裝的中間件的 process_response() 方法則會在每個response返回時被調用。
- 返回一個 Request 對象。如果其返回 Request 對象,Scrapy則停止調用 process_request方法并重新調度返回的request。當新返回的request被執行后, 相應地中間件鏈將會根據下載的response被調用。
- 或 raise IgnoreRequest 。如果其raise一個 IgnoreRequest 異常,則安裝的下載中間件的 process_exception() 方法會被調用。如果沒有任何一個方法處理該異常, 則request的errback(Request.errback)方法會被調用。如果沒有代碼處理拋出的異常, 則該異常被忽略且不記錄(不同于其他異常那樣)。
解釋:
?
?
?process_response(request, response, spider):
參數:
- request (Request 對象) – response所對應的request
- response (Response 對象) – 被處理的response
- spider (Spider 對象) – response所對應的spider
返回值:process_request() 必須返回以下之一:
- 返回一個 Response 對象。如果其返回一個 Response (可以與傳入的response相同,也可以是全新的對象), 該response會被在鏈中的其他中間件的 process_response() 方法處理。
- 返回一個 Request 對象。如果其返回一個 Request 對象,則中間件鏈停止, 返回的request會被重新調度下載。處理類似于 process_request() 返回request所做的那樣。
- 或 raise 一個 IgnoreRequest 異常。如果其拋出一個 IgnoreRequest 異常,則調用request的errback(Request.errback)。 如果沒有代碼處理拋出的異常,則該異常被忽略且不記錄(不同于其他異常那樣)。
解釋:
?
?
process_exception(request, exception, spider):
?
當下載處理器 ( download handler ) 或 process_request() ( 下載中間件 ) 拋出異常 ( 包括 IgnoreRequest 異常) 時, Scrapy 調用 process_exception() 。
參數:
- request (是 Request 對象) – 產生異常的request
- exception (Exception 對象) – 拋出的異常
- spider (Spider 對象) – request對應的spider
返回值:process_exception() 應該返回以下之一:
- 返回 None?。如果其返回 None ,Scrapy將會繼續處理該異常,接著調用已安裝的其他中間件的 process_exception() 方法,直到所有中間件都被調用完畢,則調用默認的異常處理。
- 返回一個 Response 對象。如果其返回一個 Response 對象,則已安裝的中間件鏈的 process_response() 方法被調用。Scrapy將不會調用任何其他中間件的 process_exception() 方法。
- 或者一個 Request 對象。如果其返回一個 Request 對象, 則返回的request將會被重新調用下載。這將停止中間件的 process_exception() 方法執行,就如返回一個response的那樣。
解釋:
?
?
from_crawler(cls, crawler):
?
If present, this classmethod is called to create a middleware instance from a Crawler. It must return a new instance of the middleware. Crawler object provides access to all Scrapy core components like settings and signals; it is a way for middleware to access them and hook its functionality into Scrapy.
| crawler (Crawler object) – crawler that uses this middleware |
scrapy 中的 from_crawler:https://www.jianshu.com/p/e9ec5d7b6204
解釋:
?
?
?
示例:代理中間件
?
在爬蟲開發中,更換代理IP是非常常見的情況,有時候每一次訪問都需要隨機選擇一個代理IP來進行。
中間件本身是一個Python的類,只要爬蟲每次訪問網站之前都先“經過”這個類,它就能給請求換新的代理IP,這樣就能實現動態改變代理。
在創建一個Scrapy工程以后,工程文件夾下會有一個 middlewares.py 文件,打開以后其內容如下圖所示。
Scrapy 自動生成的這個文件名稱為 middlewares.py,名字后面的 s 表示復數,說明這個文件里面可以放很多個中間件。可以看到有一個? SpiderMiddleware (爬蟲中間件)中間件 和 DownloaderMiddleware (下載中間件)中間件
?
在middlewares.py中添加下面一段代碼(可以在 下載中間件這個類 里面寫,也可以把 爬蟲中間件 和 下載中間件 這兩個類刪了,自己寫個 下載中間件的類。推薦 自己單寫一個類 作為 下載中間件):
# -*- coding: utf-8 -*-# Define here the models for your spider middleware # # See documentation in: # https://doc.scrapy.org/en/latest/topics/spider-middleware.htmlimport random from scrapy.conf import settings from scrapy.utils.project import get_project_settingsclass ProxyMiddleware(object):def process_request(self, request, spider):proxy_1 = random.choice(settings['PROXIES']) # 方法 1proxy_2 = random.choice(get_project_settings()['PROXIES']) # 方法 2request.meta['proxy'] = proxy_1打開 setting.py 添加 代理 ,并激活 這個代理中間件:
需要注意的是,代理IP是有類型的,需要先看清楚是 HTTP型 的代理IP還是 HTTPS型 的代理IP。
DOWNLOADER_MIDDLEWARES = {'test_spider.middlewares.ProxyMiddleware': 543,# 'test_spider.middlewares.Custom_B_DownloaderMiddleware': 643,# 'test_spider.middlewares.Custom_B_DownloaderMiddleware': None, }PROXIES = ['https://114.217.243.25:8118','https://125.37.175.233:8118','http://1.85.116.218:8118' ]DOWNLOADER_MIDDLEWARES 其實就是一個字典,字典的Key就是用點分隔的中間件路徑,后面的數字表示這種中間件的順序。由于中間件是按順序運行的,因此如果遇到后一個中間件依賴前一個中間件的情況,中間件的順序就至關重要。
如何確定后面的數字應該怎么寫呢?最簡單的辦法就是從543開始,逐漸加一,這樣一般不會出現什么大問題。如果想把中間件做得更專業一點,那就需要知道Scrapy自帶中間件的順序,如圖下圖所示 (?DOWNLOADER_MIDDLEWARES )。
數字越小的中間件越先執行(數字越小,越靠近引擎,數字越大越靠近下載器,所以數字越小的,processrequest()優先處理;數字越大的,process_response()優先處理;若需要關閉某個中間件直接設為None即可),例如Scrapy自帶的第1個中間件RobotsTxtMiddleware,它的作用是首先查看settings.py中ROBOTSTXT_OBEY 這一項的配置是True還是False。如果是True,表示要遵守Robots.txt協議,它就會檢查將要訪問的網址能不能被運行訪問,如果不被允許訪問,那么直接就取消這一次請求,接下來的和這次請求有關的各種操作全部都不需要繼續了。
開發者自定義的中間件,會被按順序插入到Scrapy自帶的中間件中。爬蟲會按照從100~900的順序依次運行所有的中間件。直到所有中間件全部運行完成,或者遇到某一個中間件而取消了這次請求。
?
Scrapy 其實自帶了 UA 中間件(UserAgentMiddleware)、代理中間件(HttpProxyMiddleware)和重試中間件(RetryMiddleware)。所以,從“原則上”說,要自己開發這3個中間件,需要先禁用Scrapy里面自帶的這3個中間件。要禁用Scrapy的中間件,需要在settings.py里面將這個中間件的順序設為None:
DOWNLOADER_MIDDLEWARES = {'test_spider.middlewares.ProxyMiddleware': 543,'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': None }PROXIES = ['https://114.217.243.25:8118','https://125.37.175.233:8118','http://1.85.116.218:8118' ]為什么說“原則上”應該禁用呢?先查看Scrapy自帶的代理中間件的源代碼,如下圖所示:
從上圖可以看出,如果Scrapy發現這個請求已經被設置了代理,那么這個中間件就會什么也不做,直接返回。因此雖然Scrapy自帶的這個代理中間件順序為750,比開發者自定義的代理中間件的順序543大,但是它并不會覆蓋開發者自己定義的代理信息,所以即使不禁用系統自帶的這個代理中間件也沒有關系。
代理中間件的可用代理列表不一定非要寫在settings.py里面,也可以將它們寫到數據庫或者Redis中。一個可行的自動更換代理的爬蟲系統,應該有如下的3個功能。
- 1. 有一個小爬蟲ProxySpider去各大代理網站爬取免費代理并驗證,將可以使用的代理IP保存到數據庫中。
- 2. 在ProxyMiddlerware的process_request中,每次從數據庫里面隨機選擇一條代理IP地址使用。
- 3. 周期性驗證數據庫中的無效代理,及時將其刪除。由于免費代理極其容易失效,因此如果有一定開發預算的話,建議購買專業代理機構的代理服務,高速而穩定。
?
scrapy 中對接 selenium
from scrapy.http import HtmlResponse from selenium import webdriver from selenium.common.exceptions import TimeoutException from gp.configs import *class ChromeDownloaderMiddleware(object):def __init__(self):options = webdriver.ChromeOptions()options.add_argument('--headless') # 設置無界面if CHROME_PATH:options.binary_location = CHROME_PATHif CHROME_DRIVER_PATH:self.driver = webdriver.Chrome(chrome_options=options, executable_path=CHROME_DRIVER_PATH) # 初始化Chrome驅動else:self.driver = webdriver.Chrome(chrome_options=options) # 初始化Chrome驅動def __del__(self):self.driver.close()def process_request(self, request, spider):try:print('Chrome driver begin...')self.driver.get(request.url) # 獲取網頁鏈接內容return HtmlResponse(url=request.url, body=self.driver.page_source, request=request, encoding='utf-8',status=200) # 返回HTML數據except TimeoutException:return HtmlResponse(url=request.url, request=request, encoding='utf-8', status=500)finally:print('Chrome driver end...')?
?
示例:UA (user-agent) 中間件
?
Scrapy學習篇(十一)之設置隨機User-Agent:https://www.cnblogs.com/cnkai/p/7401343.html
開發UA中間件和開發代理中間件幾乎一樣,它也是從 settings.py 配置好的 UA 列表中隨機選擇一項,加入到請求頭中。代碼如下:
class UAMiddleware(object):def process_request(self, request, spider):ua = random.choice(settings['USER_AGENT_LIST'])request.headers['User-Agent'] = ua比IP更好的是,UA不會存在失效的問題,所以只要收集幾十個UA,就可以一直使用。常見的UA如下:
USER_AGENT_LIST = ["Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; Nexus S Build/GRK39F) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1","Avant Browser/1.2.789rel1 (http://www.avantbrowser.com)","Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5","Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.310.0 Safari/532.9","Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7","Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14","Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14","Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.27 (KHTML, like Gecko) Chrome/12.0.712.0 Safari/534.27","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1","Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.120 Safari/535.2","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7","Mozilla/5.0 (Windows; U; Windows NT 6.0 x64; en-US; rv:1.9pre) Gecko/2008072421 Minefield/3.0.2pre","Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10","Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.0.11) Gecko/2009060215 Firefox/3.0.11 (.NET CLR 3.5.30729)","Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 GTB5","Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)","Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1","Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1","Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0","Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110622 Firefox/6.0a2","Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1","Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b4pre) Gecko/20100815 Minefield/4.0b4pre","Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0 )","Mozilla/4.0 (compatible; MSIE 5.5; Windows 98; Win 9x 4.90)","Mozilla/5.0 (Windows; U; Windows XP) Gecko MultiZilla/1.6.1.0a","Mozilla/2.02E (Win95; U)","Mozilla/3.01Gold (Win95; I)","Mozilla/4.8 [en] (Windows NT 5.1; U)","Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.4) Gecko Netscape/7.1 (ax)","HTC_Dream Mozilla/5.0 (Linux; U; Android 1.5; en-ca; Build/CUPCAKE) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1","Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.2; U; de-DE) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.40.1 Safari/534.6 TouchPad/1.0","Mozilla/5.0 (Linux; U; Android 1.5; en-us; sdk Build/CUPCAKE) AppleWebkit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1","Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17","Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1","Mozilla/5.0 (Linux; U; Android 1.5; en-us; htc_bahamas Build/CRB17) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1","Mozilla/5.0 (Linux; U; Android 2.1-update1; de-de; HTC Desire 1.19.161.5 Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17","Mozilla/5.0 (Linux; U; Android 2.2; en-us; Sprint APA9292KT Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1","Mozilla/5.0 (Linux; U; Android 1.5; de-ch; HTC Hero Build/CUPCAKE) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1","Mozilla/5.0 (Linux; U; Android 2.2; en-us; ADR6300 Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1","Mozilla/5.0 (Linux; U; Android 2.1; en-us; HTC Legend Build/cupcake) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17","Mozilla/5.0 (Linux; U; Android 1.5; de-de; HTC Magic Build/PLAT-RC33) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1 FirePHP/0.3","Mozilla/5.0 (Linux; U; Android 1.6; en-us; HTC_TATTOO_A3288 Build/DRC79) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1","Mozilla/5.0 (Linux; U; Android 1.0; en-us; dream) AppleWebKit/525.10 (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2","Mozilla/5.0 (Linux; U; Android 1.5; en-us; T-Mobile G1 Build/CRB43) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari 525.20.1","Mozilla/5.0 (Linux; U; Android 1.5; en-gb; T-Mobile_G2_Touch Build/CUPCAKE) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1","Mozilla/5.0 (Linux; U; Android 2.0; en-us; Droid Build/ESD20) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17","Mozilla/5.0 (Linux; U; Android 2.2; en-us; Droid Build/FRG22D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1","Mozilla/5.0 (Linux; U; Android 2.0; en-us; Milestone Build/ SHOLS_U2_01.03.1) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17","Mozilla/5.0 (Linux; U; Android 2.0.1; de-de; Milestone Build/SHOLS_U2_01.14.0) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17","Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/525.10 (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2","Mozilla/5.0 (Linux; U; Android 0.5; en-us) AppleWebKit/522 (KHTML, like Gecko) Safari/419.3","Mozilla/5.0 (Linux; U; Android 1.1; en-gb; dream) AppleWebKit/525.10 (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2","Mozilla/5.0 (Linux; U; Android 2.0; en-us; Droid Build/ESD20) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17","Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17","Mozilla/5.0 (Linux; U; Android 2.2; en-us; Sprint APA9292KT Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1","Mozilla/5.0 (Linux; U; Android 2.2; en-us; ADR6300 Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1","Mozilla/5.0 (Linux; U; Android 2.2; en-ca; GT-P1000M Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1","Mozilla/5.0 (Linux; U; Android 3.0.1; fr-fr; A500 Build/HRI66) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13","Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/525.10 (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2","Mozilla/5.0 (Linux; U; Android 1.6; es-es; SonyEricssonX10i Build/R1FA016) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1","Mozilla/5.0 (Linux; U; Android 1.6; en-us; SonyEricssonX10i Build/R1AA056) AppleWebKit/528.5 (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1", ]test_spider.py (使用 的是 scrapy-redis 的 RedisSpider,需要從 redis 讀取 url):
#!/usr/bin/python3 # -*- coding: utf-8 -*- # @Author : # @File : mao_yan_spider.py # @Software : PyCharm # @description : XXXfrom scrapy import Spider from scrapy_redis.spiders import RedisSpiderclass TestSpider(RedisSpider):name = 'test'redis_key = 'start_urls:{0}'.format(name)# start_urls = ['http://exercise.kingname.info/exercise_middleware_ua']def parse(self, response):print('response text : {0}'.format(response.text))passsetting.py 配置 ():
test.py:
#!/usr/bin/python3 # -*- coding: utf-8 -*- # @Author : # @File : test_s.py # @Software : PyCharm # @description : XXXimport redis from scrapy import cmdlinedef add_test_task():r = redis.Redis(host='127.0.0.1', port=6379)for i in range(10):url = 'http://exercise.kingname.info/exercise_middleware_ua'r.lpush('start_urls:test', url)if __name__ == "__main__":add_test_task()cmdline.execute("scrapy crawl test".split())pass運行 test.py 截圖:
可以看到 請求的 user-agent 是變化的。
from faker import Fakerclass UserAgent_Middleware():def process_request(self, request, spider):f = Faker()agent = f.firefox()request.headers['User-Agent'] = agent?
?
Cookies 中間件
?
對于需要登錄的網站,可以使用Cookies來保持登錄狀態。那么如果單獨寫一個小程序,用Selenium持續不斷地用不同的賬號登錄網站,就可以得到很多不同的Cookies。由于Cookies本質上就是一段文本,所以可以把這段文本放在Redis里面。這樣一來,當Scrapy爬蟲請求網頁時,可以從Redis中讀取Cookies并給爬蟲換上。這樣爬蟲就可以一直保持登錄狀態。
以下面這個練習頁面為例:http://exercise.kingname.info/exercise_login_success
如果直接用Scrapy訪問,得到的是登錄界面的源代碼,如下圖所示。
現在,使用中間件,可以實現完全不改動這個loginSpider.py里面的代碼,就打印出登錄以后才顯示的內容。
首先開發一個小程序,通過Selenium登錄這個頁面,并將網站返回的Headers保存到Redis中。這個小程序的代碼如下圖所示。
這段代碼的作用是使用Selenium和ChromeDriver填寫用戶名和密碼,實現登錄練習頁面,然后將登錄以后的Cookies轉換為JSON格式的字符串并保存到Redis中。
接下來,再寫一個中間件,用來從Redis中讀取Cookies,并把這個Cookies給Scrapy使用:
class LoginMiddleware(object):def __init__(self):self.client = redis.StrictRedis()def process_request(self, request, spider):if spider.name == 'loginSpider':cookies = json.loads(self.client.lpop('cookies').decode())request.cookies = cookies設置了這個中間件以后,爬蟲里面的代碼不需要做任何修改就可以成功得到登錄以后才能看到的HTML,如圖12-12所示。
如果有某網站的100個賬號,那么單獨寫一個程序,持續不斷地用Selenium和ChromeDriver或者Selenium 和PhantomJS登錄,獲取Cookies,并將Cookies存放到Redis中。爬蟲每次訪問都從Redis中讀取一個新的Cookies來進行爬取,就大大降低了被網站發現或者封鎖的可能性。
這種方式不僅適用于登錄,也適用于驗證碼的處理。
?
?
內置 下載中間件 參考手冊
?
本頁面介紹了Scrapy自帶的所有下載中間件。關于如何使用及編寫您自己的中間件,請參考 downloader middleware usage guide.
關于默認啟用的中間件列表(及其順序)請參考 DOWNLOADER_MIDDLEWARES_BASE 設置。
?
?
CookiesMiddleware
?
class scrapy.downloadermiddlewares.cookies.CookiesMiddleware
該中間件使得爬取需要cookie(例如使用session)的網站成為了可能。 其追蹤了web server發送的cookie,并在之后的request中發送回去, 就如瀏覽器所做的那樣。
以下設置可以用來配置cookie中間件:
- COOKIES_ENABLED
- COOKIES_DEBUG
每個 spider 多 cookie session
0.15 新版功能.
Scrapy 通過使用 cookiejar 作為 Request meta 的 key 來支持單 spider 追蹤多 cookie session。 默認情況下其使用一個 cookie jar(session),不過您可以傳遞一個標示符來使用多個。
例如 ( yield 的 每個 Request 都 有一個 meta={'cookiejar': i} ,cookiejar 這個字段的 值只是一個標識,只要不為 None 就行,通常 設置為一個整數 。例如 1,或者 True):
for i, url in enumerate(urls): yield scrapy.Request(url="http://www.example.com", meta={'cookiejar': i}, callback=self.parse_page)需要注意的是 cookiejar meta key不是”黏性的(sticky)”。 您需要在之后的 每個 request 請求中接著傳遞。例如:
def parse_page(self, response):# do some processingreturn scrapy.Request("http://www.example.com/otherpage",meta={'cookiejar': response.meta['cookiejar']},callback=self.parse_other_page)?
COOKIES_ENABLED
默認: True
是否啟用 cookies middleware。如果關閉,cookies 將不會發送給 web server。
COOKIES_DEBUG
默認: False
如果啟用,Scrapy將記錄所有在request(Cookie 請求頭)發送的cookies及response接收到的cookies(Set-Cookie 接收頭)。
下邊是啟用 COOKIES_DEBUG 的記錄的樣例:
2011-04-06 14:35:10-0300 [scrapy] INFO: Spider opened 2011-04-06 14:35:10-0300 [scrapy] DEBUG: Sending cookies to: <GET http://www.diningcity.com/netherlands/index.html>Cookie: clientlanguage_nl=en_EN 2011-04-06 14:35:14-0300 [scrapy] DEBUG: Received cookies from: <200 http://www.diningcity.com/netherlands/index.html>Set-Cookie: JSESSIONID=B~FA4DC0C496C8762AE4F1A620EAB34F38; Path=/Set-Cookie: ip_isocode=USSet-Cookie: clientlanguage_nl=en_EN; Expires=Thu, 07-Apr-2011 21:21:34 GMT; Path=/ 2011-04-06 14:49:50-0300 [scrapy] DEBUG: Crawled (200) <GET http://www.diningcity.com/netherlands/index.html> (referer: None) [...]?
?
DefaultHeadersMiddleware
?
class scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware
該中間件設置 DEFAULT_REQUEST_HEADERS 指定的默認request header。
?
?
DownloadTimeoutMiddleware
?
class scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware
該中間件設置 DOWNLOAD_TIMEOUT 指定的request下載超時時間.
Note
You can also set download timeout per-request using download_timeout Request.meta key; this is supported even when DownloadTimeoutMiddleware is disabled.
?
?
HttpAuthMiddleware
?
class scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware
該中間件完成某些使用 Basic access authentication (或者叫HTTP認證)的spider生成的請求的認證過程。
在 spider 中啟用 HTTP 認證,請設置 spider 的 http_user 及 http_pass 屬性。
樣例:
from scrapy.spiders import CrawlSpiderclass SomeIntranetSiteSpider(CrawlSpider):http_user = 'someuser'http_pass = 'somepass'name = 'intranet.example.com'# .. rest of the spider code omitted ...?
?
HttpCacheMiddleware
?
class scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware
該中間件為所有HTTP request及response提供了底層(low-level)緩存支持。 其由cache存儲后端及cache策略組成。
Scrapy提供了兩種HTTP緩存存儲后端:
- Filesystem storage backend (默認值)
- DBM storage backend
您可以使用 HTTPCACHE_STORAGE 設定來修改HTTP緩存存儲后端。 您也可以實現您自己的存儲后端。
Scrapy提供了兩種了緩存策略:
- RFC2616策略
- Dummy策略(默認值)
您可以使用 HTTPCACHE_POLICY 設定來修改HTTP緩存存儲后端。 您也可以實現您自己的存儲策略。
?
Dummy策略(默認值)
該策略不考慮任何HTTP Cache-Control指令。每個request及其對應的response都被緩存。 當相同的request發生時,其不發送任何數據,直接返回response。
Dummpy策略對于測試spider十分有用。其能使spider運行更快(不需要每次等待下載完成), 同時在沒有網絡連接時也能測試。其目的是為了能夠回放spider的運行過程, 使之與之前的運行過程一模一樣 。
使用這個策略請設置:
- HTTPCACHE_POLICY 為 scrapy.extensions.httpcache.DummyPolicy
?
RFC2616策略
該策略提供了符合RFC2616的HTTP緩存,例如符合HTTP Cache-Control, 針對生產環境并且應用在持續性運行環境所設置。該策略能避免下載未修改的數據(來節省帶寬,提高爬取速度)。
實現了:
-
當 no-store cache-control指令設置時不存儲response/request。
-
當 no-cache cache-control指定設置時不從cache中提取response,即使response為最新。
-
根據 max-age cache-control指令中計算保存時間(freshness lifetime)。
-
根據 Expires 指令來計算保存時間(freshness lifetime)。
-
根據response包頭的 Last-Modified 指令來計算保存時間(freshness lifetime)(Firefox使用的啟發式算法)。
-
根據response包頭的 Age 計算當前年齡(current age)
-
根據 Date 計算當前年齡(current age)
-
根據response包頭的 Last-Modified 驗證老舊的response。
-
根據response包頭的 ETag 驗證老舊的response。
-
為接收到的response設置缺失的 Date 字段。
-
支持request中cache-control指定的 max-stale
通過該字段,使得spider完整支持了RFC2616緩存策略,但避免了多次請求下情況下的重驗證問題(revalidation on a request-by-request basis). 后者仍然需要HTTP標準進行確定.
例子:
在Request的包頭中添加 Cache-Control: max-stale=600 表明接受未超過600秒的超時時間的response.
更多請參考: RFC2616, 14.9.3
目前仍然缺失:
- Pragma: no-cache 支持 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1
- Vary 字段支持 http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.6
- 當update或delete之后失效相應的response http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10
- ... 以及其他可能缺失的特性 ..
使用這個策略,設置:
- HTTPCACHE_POLICY 為 scrapy.extensions.httpcache.RFC2616Policy
?
Filesystem storage backend (默認值)
文件系統存儲后端可以用于HTTP緩存中間件。
使用該存儲端,設置:
- HTTPCACHE_STORAGE 為 scrapy.extensions.httpcache.FilesystemCacheStorage
每個request/response組存儲在不同的目錄中,包含下列文件:
- request_body - the plain request body
- request_headers - the request headers (原始HTTP格式)
- response_body - the plain response body
- response_headers - the request headers (原始HTTP格式)
- meta - 以Python repr() 格式(grep-friendly格式)存儲的該緩存資源的一些元數據。
- pickled_meta - 與 meta 相同的元數據,不過使用pickle來獲得更高效的反序列化性能。
目錄的名稱與request的指紋(參考 scrapy.utils.request.fingerprint)有關,而二級目錄是為了避免在同一文件夾下有太多文件 (這在很多文件系統中是十分低效的)。目錄的例子:
/path/to/cache/dir/example.com/72/72811f648e718090f041317756c03adb0ada46c7?
DBM storage backend
0.13 新版功能.
同時也有 DBM 存儲后端可以用于HTTP緩存中間件。
默認情況下,其采用 anydbm 模塊,不過您也可以通過 HTTPCACHE_DBM_MODULE 設置進行修改。
使用該存儲端,設置:
- HTTPCACHE_STORAGE 為 scrapy.extensions.httpcache.DbmCacheStorage
?
LevelDB storage backend
0.23 新版功能.
A LevelDB storage backend is also available for the HTTP cache middleware.
This backend is not recommended for development because only one process can access LevelDB databases at the same time, so you can’t run a crawl and open the scrapy shell in parallel for the same spider.
In order to use this storage backend:
- set HTTPCACHE_STORAGE to scrapy.extensions.httpcache.LeveldbCacheStorage
- install LevelDB python bindings like pip install leveldb
?
HTTPCache中間件設置
HttpCacheMiddleware 可以通過以下設置進行配置:
?
HTTPCACHE_ENABLED
0.11 新版功能.
默認: False
HTTP緩存是否開啟。
在 0.11 版更改: 在0.11版本前,是使用 HTTPCACHE_DIR 來開啟緩存。
?
HTTPCACHE_EXPIRATION_SECS
默認: 0
緩存的request的超時時間,單位秒。
超過這個時間的緩存request將會被重新下載。如果為0,則緩存的request將永遠不會超時。
在 0.11 版更改: 在0.11版本前,0的意義是緩存的request永遠超時。
?
HTTPCACHE_DIR
默認: 'httpcache'
存儲(底層的)HTTP緩存的目錄。如果為空,則HTTP緩存將會被關閉。 如果為相對目錄,則相對于項目數據目錄(project data dir)。更多內容請參考 默認的Scrapy項目結構 。
?
HTTPCACHE_IGNORE_HTTP_CODES
0.10 新版功能.
默認: []
不緩存設置中的HTTP返回值(code)的request。
?
HTTPCACHE_IGNORE_MISSING
默認: False
如果啟用,在緩存中沒找到的request將會被忽略,不下載。
?
HTTPCACHE_IGNORE_SCHEMES
0.10 新版功能.
默認: ['file']
不緩存這些URI標準(scheme)的response。
?
HTTPCACHE_STORAGE
默認: 'scrapy.extensions.httpcache.FilesystemCacheStorage'
實現緩存存儲后端的類。
?
HTTPCACHE_DBM_MODULE
0.13 新版功能.
默認: 'anydbm'
在 DBM存儲后端 的數據庫模塊。 該設定針對DBM后端。
?
HTTPCACHE_POLICY
0.18 新版功能.
默認: 'scrapy.extensions.httpcache.DummyPolicy'
實現緩存策略的類。
?
HTTPCACHE_GZIP
0.25 新版功能.
默認: False
如果啟用,scrapy將會使用gzip壓縮所有緩存的數據. 該設定只針對文件系統后端(Filesystem backend)有效。
?
?
HttpCompressionMiddleware
?
class scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware
該中間件提供了對壓縮(gzip, deflate)數據的支持。
HttpCompressionMiddleware Settings
COMPRESSION_ENABLED
默認: True
Compression Middleware(壓縮中間件)是否開啟。
?
?
ChunkedTransferMiddleware
?
class scrapy.downloadermiddlewares.chunked.ChunkedTransferMiddleware
該中間件添加了對 chunked transfer encoding 的支持。
?
?
HttpProxyMiddleware
?
0.8 新版功能.
class scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware
該中間件提供了對request設置HTTP代理的支持。您可以通過在 Request 對象中設置 proxy 元數據來開啟代理。
類似于Python標準庫模塊 urllib 及 urllib2 ,其使用了下列環境變量:
- http_proxy
- https_proxy
- no_proxy
您也可以針對每個請求設置 proxy 元數據, 其形式類似于 http://some_proxy_server:port.
?
?
RedirectMiddleware
?
class scrapy.downloadermiddlewares.redirect.RedirectMiddleware
該中間件根據 response 的狀態處理重定向的request。
通過該中間件的(被重定向的)request的url可以通過 Request.meta 的 redirect_urls 鍵找到。
RedirectMiddleware 可以通過下列設置進行配置(更多內容請參考設置文檔):
- REDIRECT_ENABLED
- REDIRECT_MAX_TIMES
如果 Request.meta 包含 dont_redirect 鍵,則該 request 將會被此中間件忽略。
如果想要處理一些 重定向狀態碼 在你的 spider 中,你可以 spider 的屬性 handle_httpstatus_list 列出。
例如,如果想要 重定向中間件 忽略 301 和 302 的 response(通過其他的狀態碼)你可以這樣寫:
class MySpider(CrawlSpider):handle_httpstatus_list = [301, 302]Request.meta 的鍵 handle_httpstatus_list ? 能被用來指定 每個 request 的 response 的狀態碼應該被允許通過。
也可以設置 meta 的 key 為 handle_httpstatus_all? 值為 True 來允許 一個 請求的所有 response 通過
?
RedirectMiddleware settings
REDIRECT_ENABLED
0.13 新版功能.
默認: True
是否啟用Redirect中間件。
?
REDIRECT_MAX_TIMES
默認: 20
單個request被重定向的最大次數。
?
?
MetaRefreshMiddleware
?
class scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware
該中間件根據meta-refresh html標簽處理request重定向。
MetaRefreshMiddleware 可以通過以下設定進行配置 (更多內容請參考設置文檔)。
- METAREFRESH_ENABLED
- METAREFRESH_MAXDELAY
該中間件遵循 RedirectMiddleware 描述的 REDIRECT_MAX_TIMES 設定,dont_redirect 及 redirect_urls meta key。
MetaRefreshMiddleware settings
METAREFRESH_ENABLED
0.17 新版功能.
默認: True
Meta Refresh中間件是否啟用。
REDIRECT_MAX_METAREFRESH_DELAY
默認: 100
跟進重定向的最大 meta-refresh 延遲(單位:秒)。
?
?
RetryMiddleware
?
class scrapy.downloadermiddlewares.retry.RetryMiddleware
該中間件將重試可能由于臨時的問題,例如連接超時或者HTTP 500錯誤導致失敗的頁面。
爬取進程會收集失敗的頁面并在最后,spider爬取完所有正常(不失敗)的頁面后重新調度。 一旦沒有更多需要重試的失敗頁面,該中間件將會發送一個信號(retry_complete), 其他插件可以監聽該信號。
RetryMiddleware 可以通過下列設定進行配置 (更多內容請參考設置文檔):
- RETRY_ENABLED
- RETRY_TIMES
- RETRY_HTTP_CODES
關于HTTP錯誤的考慮:
如果根據HTTP協議,您可能想要在設定 RETRY_HTTP_CODES 中移除400錯誤。 該錯誤被默認包括是由于這個代碼經常被用來指示服務器過載(overload)了。而在這種情況下,我們想進行重試。
如果 Request.meta 包含 dont_retry 鍵, 該request將會被本中間件忽略。
RetryMiddleware Settings
RETRY_ENABLED
0.13 新版功能.
默認: True
Retry Middleware是否啟用。
RETRY_TIMES
默認: 2
包括第一次下載,最多的重試次數
RETRY_HTTP_CODES
默認: [500, 502, 503, 504, 400, 408]
重試的response 返回值(code)。其他錯誤(DNS查找問題、連接失敗及其他)則一定會進行重試。
?
?
RobotsTxtMiddleware
?
class scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware
該中間件過濾所有robots.txt eclusion standard中禁止的request。
確認該中間件及 ROBOTSTXT_OBEY 設置被啟用以確保Scrapy尊重robots.txt。
警告
記住, 如果您在一個網站中使用了多個并發請求, Scrapy仍然可能下載一些被禁止的頁面。這是由于這些頁面是在robots.txt被下載前被請求的。 這是當前robots.txt中間件已知的限制,并將在未來進行修復。
?
?
DownloaderStats
?
class scrapy.downloadermiddlewares.stats.DownloaderStats
保存所有通過的request、response及exception的中間件。
您必須啟用 DOWNLOADER_STATS 來啟用該中間件。
?
?
UserAgentMiddleware
?
class scrapy.downloadermiddlewares.useragent.UserAgentMiddleware
用于覆蓋spider的默認user agent的中間件。
要使得spider能覆蓋默認的user agent,其 user_agent 屬性必須被設置。
?
?
AjaxCrawlMiddleware
?
class scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware
根據meta-fragment html標簽查找 ‘AJAX可爬取’ 頁面的中間件。查看 https://developers.google.com/webmasters/ajax-crawling/docs/getting-started 來獲得更多內容。
注解
即使沒有啟用該中間件,Scrapy仍能查找類似于 'http://example.com/!#foo=bar' 這樣的’AJAX可爬取’頁面。 AjaxCrawlMiddleware是針對不具有 '!#' 的URL,通常發生在’index’或者’main’頁面中。
AjaxCrawlMiddleware 設置
AJAXCRAWL_ENABLED
0.21 新版功能.
默認: False
AjaxCrawlMiddleware是否啟用。您可能需要針對 通用爬蟲 啟用該中間件。
?
HttpProxyMiddleware settings
HTTPPROXY_ENABLED
Default: True
Whether or not to enable the HttpProxyMiddleware.
HTTPPROXY_AUTH_ENCODING
Default: "latin-1"
The default encoding for proxy authentication on HttpProxyMiddleware.
?
?
?
?
總結
以上是生活随笔為你收集整理的Scrapy 下载器 中间件(Downloader Middleware)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Flask --- 框架快速入门
- 下一篇: 【进阶】 --- 多线程、多进程、异步I