scrapy組件
首先我們看下scrapy官網(wǎng)提供的新結(jié)構(gòu)圖乘盼,乍一看這畫的是啥啊凉夯,這需要你慢慢的理解其原理就很容易看懂了隐轩,這些都是一個(gè)通用爬蟲框架該具有的一些基本組件。上一篇博客說了項(xiàng)目管道(也就是圖中的ITEM PIPELINES)物喷,可以看到中間的引擎(ENGINE)將item傳遞給了項(xiàng)目管道卤材,也就是讓項(xiàng)目管道來處理抓取到的內(nèi)容。另外圖中的所謂的組件只是抽象出來的東西比較容易讓人理解峦失,其實(shí)這些都是python的類實(shí)例化的東西扇丛。
下載中間件處于引擎和下載器的中間,就像是引擎和下載器有一根管道尉辑,管道上面有兩個(gè)路障就是下載中間件了帆精,而引擎和下載器之間只傳遞請(qǐng)求(REQUESTS)和響應(yīng)(RESPONSE),所以這兩個(gè)路障很明顯的作用就是攔截請(qǐng)求和響應(yīng)(至于攔截之后想干嘛就隨便了隧魄,比如更改請(qǐng)求或響應(yīng)內(nèi)容卓练,更改請(qǐng)求頭或響應(yīng)頭,或者什么都不干购啄,記錄一下就放過去)襟企。
蜘蛛中間件處于蜘蛛和引擎之間,這里說的蜘蛛就是我們?cè)趕pider目錄下編寫的蜘蛛類狮含,已經(jīng)寫過蜘蛛的應(yīng)該知道顽悼,在蜘蛛中一般會(huì)處理響應(yīng)(默認(rèn)解析是parse方法),返回item,或者返回(準(zhǔn)確點(diǎn)應(yīng)該是迭代)請(qǐng)求(scrapy.Request)辉川。而在圖中也很明顯表蝙,蜘蛛和引擎之間會(huì)傳遞請(qǐng)求(REQUESTS)、響應(yīng)(RESPONSE)和items乓旗。響應(yīng)是由下載器從網(wǎng)站上下載得到的府蛇,在傳遞給引擎后給蜘蛛來處理,而items一般是由蜘蛛從響應(yīng)內(nèi)容中解析出來傳遞給引擎在給項(xiàng)目管道處理屿愚,而請(qǐng)求一般是由蜘蛛中的start_urls中定義的URL和蜘蛛解析出的新的URL準(zhǔn)備傳遞給引擎在交由調(diào)度器統(tǒng)一管理汇跨。
上面的一堆屁話說的:中間件的作用就是攔截兩個(gè)組件傳遞的內(nèi)容。
看了這些組件的功能和使用你會(huì)發(fā)現(xiàn)為什么要這么麻煩妆距,你看我用requests庫(kù)只需要請(qǐng)求一下得到響應(yīng)直接處理就行了穷遂,這引擎和調(diào)度器感覺沒什么存在的意義啊。這就需要慢慢理解了娱据,或者自己寫一個(gè)簡(jiǎn)單通用爬蟲框架可能就知道了蚪黑。
中間件的編寫
中間件的啟用
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'newspider.middlewares.UAMiddleware': 500 #newspider為scrapy項(xiàng)目名稱,也就是settings.py中BOT_NAME的值,UAMiddleware為定義的中間件的名字
}
字典的值如何選擇中剩,500這個(gè)值改成其他的行不行呢忌穿?先看一下官方是怎么說的:
scrapy會(huì)將DOWNLOADER_MIDDLEWARES設(shè)置與DOWNLOADER_MIDDLEWARES_BASE設(shè)置合并,然后按值的順序排序以獲取啟用的中間件的最終排序列表:值最小的中間件離引擎更近结啼,最大的中間件離下載器更近掠剑。換句話說,每個(gè)中間件的process_request() 方法將以遞增的順序被調(diào)用郊愧,而每個(gè)中間件的process_response()方法將以遞減的順序調(diào)用朴译。
要決定分配給中間件的順序井佑,請(qǐng)查看 DOWNLOADER_MIDDLEWARES_BASE設(shè)置并根據(jù)您要插入中間件的位置選擇一個(gè)值。順序很重要眠寿,因?yàn)槊總€(gè)中間件執(zhí)行不同的操作躬翁,并且您的中間件可能取決于所應(yīng)用的某些先前(或后續(xù))中間件。
如果要禁用內(nèi)置中間件(DOWNLOADER_MIDDLEWARES_BASE默認(rèn)情況下在定義和啟用的中間件 )澜公,則必須在項(xiàng)目的DOWNLOADER_MIDDLEWARES設(shè)置為None姆另。
上面的一些話中有一句很重要:每個(gè)中間件的process_request() 方法將以遞增的順序被調(diào)用,而每個(gè)中間件的process_response()方法將以遞減的順序調(diào)用坟乾。這應(yīng)該就很清楚值怎么選吧迹辐,不過一般中間件并沒有確定的順序,只要不產(chǎn)生矛盾就行了甚侣。
舉個(gè)例子吧:你已經(jīng)寫了一個(gè)中間件newspider.middlewares.UAMiddleware明吩,但是你在啟用的時(shí)候沒有禁止內(nèi)置的UserAgentMiddleware,怎么才能使你的中間件生效呢殷费?代碼如下:
DOWNLOADER_MIDDLEWARES = {
#'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'newspider.middlewares.UAMiddleware': 501 #newspider為scrapy項(xiàng)目名稱,也就是settings.py中BOT_NAME的值印荔,UAMiddleware為定義的中間件的名字
}
因?yàn)槟J(rèn)的UserAgentMiddleware值為500,因?yàn)樾薷恼?qǐng)求頭是在process_request详羡,而process_request是以遞增的形式被調(diào)用仍律,所以會(huì)先調(diào)用內(nèi)置UserAgentMiddleware,在調(diào)用你定義的UAMiddleware实柠,則內(nèi)置的會(huì)被覆蓋水泉。不過因?yàn)楹芏嗳诵薷恼?qǐng)求頭都是以字典訪問的形式修改(request.headers["User-Agent"] = ""
),這樣的話即使你設(shè)置值為499,結(jié)果還是你設(shè)定的User-Agent窒盐,而不是默認(rèn)內(nèi)置的草则。
其實(shí)看一下內(nèi)置的代碼就可以理解了:源代碼
代碼使用的是request.headers.setdefault(b'User-Agent', self.user_agent)
,setdefault是當(dāng)不存在鍵時(shí)設(shè)置蟹漓,存在則不設(shè)置炕横。
內(nèi)置下載中間件
DOWNLOADER_MIDDLEWARES_BASE = {
'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,
}
下載中間件的編寫
- process_request(request,spider): 所有請(qǐng)求都會(huì)調(diào)用此方法
- process_response(request, response, spider): 這里的參數(shù)比上面的多了response葡粒,肯定是用來處理response的
- process_exception(request, exception, spider):處理異常
- from_crawler(cls, crawler):從settings.py獲取配置
假設(shè)我們需要一個(gè)自動(dòng)管理cookie的中間件份殿,應(yīng)該怎么編寫?首先管理cookie一般就是將服務(wù)器返回的Set-cookie更新一下已保存的cookie嗽交。偽代碼如下:
from scrapy.exceptions import NotConfigured
class CookieMiddleware(object):
def __init__(self):
# 實(shí)際上cookies并不能使用字典來保存伯铣,因?yàn)閏ookies是可以包含相同的鍵的
# 這里只做簡(jiǎn)單的演示,正陈秩遥可以使用scrapy.http.cookies.CookieJar
self.cookies = {}
def process_request(request, spider):
self.addcookies(request, self.cookies)
def process_response(request, response, spider):
newcookie = response.headers.getlist('Set-Cookie')
self.updatecookies(self.cookies, newcookie)
return response
def process_exception(request, exception, spider):
pass
def addcookies(self, request, cookies):
'''
給請(qǐng)求加cookie
'''
pass
def updatecookie(self, cookies, cookie):
'''
更新cookie字典(對(duì)象)
'''
pass
@classmethod
def from_crawler(cls, crawler):
if not crawler.settings.get('是否開啟cookie'):
#這個(gè)方法在__init__之前,拋出異常的話焚鲜,類中的所有方法就都不執(zhí)行了
raise NotConfigured
其實(shí)scrapy已經(jīng)內(nèi)置了cookies管理的中間件掌唾,可以參考一下源代碼:https://docs.scrapy.org/en/latest/_modules/scrapy/downloadermiddlewares/cookies.html#CookiesMiddleware
想要一個(gè)隨機(jī)User-Agent的中間件也很簡(jiǎn)單放前,只需要在process_request方法中改變請(qǐng)求頭就行了。其實(shí)process_request和process_response這兩個(gè)方法是有返回值的糯彬,在上面的例子中只是對(duì)請(qǐng)求頭和響應(yīng)頭做了修改凭语,所以沒有涉及到返回值。如果我們需要對(duì)請(qǐng)求和響應(yīng)整個(gè)做更改的話就需要重新構(gòu)造請(qǐng)求和響應(yīng)撩扒。這里最典型的例子就是selenium中間件了似扔。
spider示例代碼:
import scrapy
from selenium import webdriver
class SeleniumSpider(scrapy.Spider):
name = 'seleniumspider'
start_urls = ['https://example.com']
# 實(shí)例化瀏覽器對(duì)象, 因?yàn)闉g覽器對(duì)象只需要實(shí)例化一次, 所以不在中間件中進(jìn)行實(shí)例化
bro = webdriver.Chrome(executable_path=r'E:\spiderman\day13\wynews\wynews\wynews\chromedriver.exe')
def parse(self, response):
'''
和正常一樣,該怎么解析怎么解析
'''
pass
selenium中間件示例代碼(只是簡(jiǎn)單演示搓谆,為了更好理解中間件怎么寫):
from selenium.common.exceptions import TimeoutException
from scrapy.http import HtmlResponse
class SeleniumMiddleware(object):
def __init__(self, timeout=None, options=None):
pass
def process_reqeust(self, request, spider):
try:
url = request.url
spider.bro.get(url)
return HtmlResponse(url=url, body=spider.bro.page_source, request=request,encoding='utf-8',status=200)
except TimeoutException:
return HtmlResponse(url=url, status=500, request=request)
@classmethod
def from_crawler(cls, crawler):
pass
在代碼中HtmlResponse類就是我們新構(gòu)建的響應(yīng)炒辉,這樣前面的spider處理的response就是這個(gè)響應(yīng)了。這里有個(gè)疑問泉手?為什么要在process_reqeust這個(gè)方法中返回響應(yīng)黔寇,就不能在process_response中嗎?其實(shí)也可以斩萌,區(qū)別在于process_reqeust返回的話缝裤,會(huì)忽略其他中間件中的process_reqeust和process_exception方法,只執(zhí)行其他中間件的process_response方法颊郎。試想一下如果你還有一個(gè)cookie管理的中間件憋飞,process_reqeust的操作是不是就和SeleniumMiddleware中的矛盾了,所以我們要在process_reqeust返回response來使其他中間件的process_reqeust方法失效姆吭。
返回值
process_reqeust方法返回值
- None:如果返回None榛做,則一切正常傳遞
- request對(duì)象:如果返回request對(duì)象,scrapy會(huì)停止調(diào)用接下來的中間件而重新處理request對(duì)象(也就是交由引擎重新管理)
- response對(duì)象:...猾编,Scrapy不再調(diào)用其他中間件的process_request()或process_exception()方法瘤睹,它將直接調(diào)用其他中間件的process_response方法
- IgnoreRequest異常:依次調(diào)用所有中間件的process_exception方法
process_response方法返回值(注意這個(gè)方法必須返回一個(gè)對(duì)象,不能是None)
- response對(duì)象:傳遞給下一個(gè)中間件的process_response方法
- request對(duì)象:scrapy會(huì)停止調(diào)用接下來的中間件而重新處理request對(duì)象(也就是交由引擎重新管理)
- IgnoreRequest異常:依次調(diào)用所有中間件的process_exception方法
process_exception方法返回值
- None:繼續(xù)傳遞
- request對(duì)象:scrapy會(huì)停止調(diào)用接下來的中間件的process_exception方法而重新處理request對(duì)象(也就是交由引擎重新管理)
- response對(duì)象:停止傳遞接下來的中間件中的process_exception答倡,而開始調(diào)用所有process_response方法
蜘蛛中間件
- process_spider_input(response, spider):所有請(qǐng)求都會(huì)調(diào)用這個(gè)方法
- process_spider_output(response, result, spider):spider解析完response之后調(diào)用該方法轰传,result就是解析的結(jié)果(是一個(gè)可迭代對(duì)象),其中可能是items也可能是request對(duì)象
- process_spider_exception(response, exception, spider):處理異常
- process_start_requests(start_requests, spider):同process_spider_output瘪撇,不過只處理spider中start_requests方法返回的結(jié)果
- from_crawler(cls, crawler):...
同樣是攔截請(qǐng)求和響應(yīng)获茬,那么和下載中間件有什么區(qū)別呢?因?yàn)橄螺d中間件是連通引擎和下載器的倔既,所以如果修改請(qǐng)求只會(huì)影響下載器返回的結(jié)果恕曲。如果修改響應(yīng),會(huì)影響蜘蛛處理渤涌。而蜘蛛中間件是連通引擎和蜘蛛的佩谣,如果修改請(qǐng)求則會(huì)影響整個(gè)scrapy的請(qǐng)求,因?yàn)閟crapy的所有請(qǐng)求都來自于蜘蛛实蓬,當(dāng)然包括調(diào)度器和下載器茸俭。如果修改響應(yīng)吊履,則只會(huì)影響蜘蛛的解析,因?yàn)轫憫?yīng)是由引擎?zhèn)鬟f給蜘蛛的调鬓。
上面說的有點(diǎn)亂艇炎,整理一下這兩個(gè)的功能:
蜘蛛中間件:記錄深度、丟棄非200狀態(tài)碼響應(yīng)腾窝、丟棄非指定域名請(qǐng)求等
下載中間件:修改請(qǐng)求頭缀踪、修改響應(yīng)、管理cookies虹脯、丟棄非200狀態(tài)碼響應(yīng)驴娃、丟棄非指定域名請(qǐng)求等
丟棄非指定域名請(qǐng)求一般使用蜘蛛中間件,如果使用下載中間件會(huì)導(dǎo)致引擎和調(diào)度器的無效任務(wù)變多归形,丟棄非200狀態(tài)碼響應(yīng)我感覺應(yīng)該用下載中間件托慨,但是scrapy內(nèi)置的使用的是蜘蛛中間件,可能是我理解不夠透徹吧暇榴。
scrapy已經(jīng)提供了很多內(nèi)置的中間件厚棵,只需要啟用即可。另外蔼紧,蜘蛛中間件一般用于操作蜘蛛返回的request婆硬,而下載中間件用于操作向互聯(lián)網(wǎng)發(fā)起請(qǐng)求的request和返回的response。更多的示例可以看一些內(nèi)置中間件的源碼奸例,蜘蛛中間件一般不需要自己編寫彬犯,使用內(nèi)置的幾個(gè)也足夠了