Scrapy 中的中間件
今天我們來(lái)聊一聊 Scrapy 框架中的中間件使用,包括 Spider 中間件、下載中間件等。它屬于 Scrapy 框架的一個(gè)重要部分,是我們定制化 Scrapy 框架時(shí)的重要基礎(chǔ)。
1. Spider 中間件
1.1 Spider 中間件介紹
我們從架構(gòu)圖中可以看到,Spider 中間件(Spider Middlewares)位于引擎模塊和 Spiders 中間,通過(guò)這個(gè)中間件模塊我們可以控制發(fā)送給 Spider 的 Response 以及 Spider 傳回給引擎的 Items 和 Requests。使用 Spider 中間件的第一步是要在配置文件中啟用它,啟用方式只需要設(shè)置 SPIDER_MIDDLEWARES 的值即可:
SPIDER_MIDDLEWARES = {
# 指定編寫的Spider中間件類的位置
'myproject.middlewares.CustomSpiderMiddleware': 543,
}
自定義在 settings.py
中的 Spider 中間件會(huì)和 Scrapy 內(nèi)置的 SPIDER_MIDDLEWARES_BASE 設(shè)置合并,然后根據(jù)對(duì)應(yīng)的 value 值進(jìn)行排序,得到最終的 Spider 中間件的有序執(zhí)行列表:值最小的最靠近引擎那一側(cè),值最大的最靠近 Spider。
我們來(lái)看 Scrapy 內(nèi)置的 SPIDER_MIDDLEWARES_BASE 值如下所示。上面設(shè)置的 543 正好使得自定義的 CustomSpiderMiddleware 中間件位于 OffsiteMiddleware 中間件和 RefererMiddleware 中間件之間。
# 源碼位置:scrapy/settings/default_settings.py
SPIDER_MIDDLEWARES_BASE = {
# Engine side
'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,
# Spider side
}
當(dāng)我們想禁止相應(yīng)的 Spider 中間件時(shí),只需要在 SPIDER_MIDDLEWARES 中將對(duì)應(yīng)的中間件的值設(shè)置為 None 即可:
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None,
}
1.2 編寫 Spider 中間件
編寫 Scrapy 中自定義的 Spider 中間件非常簡(jiǎn)單,每個(gè)中間組件都是實(shí)現(xiàn)了以下一個(gè)或者多個(gè)方法的 Python 類:
- process_spider_input(response, spider):對(duì)于通過(guò) Spider 中間件并進(jìn)入 Spider 進(jìn)行處理的每個(gè) response,都會(huì)調(diào)用此方法。該方法返回 None 或者拋出異常,如果返回的是 None,Scrapy 將繼續(xù)處理這個(gè)響應(yīng),執(zhí)行所有其他的 Spider 中間件,直到最后,響應(yīng)被交給 Spider 進(jìn)行處理;如果是拋出了一個(gè)異常,則會(huì)調(diào)用下面的 process_spider_exception() 方法處理而不會(huì)在調(diào)用其他的 Spider 中間件;
- process_spider_output(response, result, spider):這個(gè)方法一般會(huì)在 Spider 處理完響應(yīng)后調(diào)用,也就是我們前面編寫的 parse() 或者自定義的 parse_xxx() 方法執(zhí)行完后調(diào)用;
- process_spider_exception(response, exception, spider):當(dāng) spider 或者 上一個(gè) spider 中間件的 process_spider_output() 拋出異常時(shí)會(huì)調(diào)用該方法;
- process_start_requests(start_requests, spider):此方法與 spider 的開始請(qǐng)求一起調(diào)用,其工作方式與 process_spider_output() 方法類似,只不過(guò)它沒(méi)有關(guān)聯(lián)的 response,且只返回請(qǐng)求 (而不是 items)。
注意:我們一般實(shí)現(xiàn)的自定義中間件類會(huì)寫在 scrapy 項(xiàng)目的 middlewares.py
中,當(dāng)然也可以寫到任意的代碼文件中,只需要在寫入配置時(shí)指定好完整的類路徑即可。
1.3 Scrapy 中內(nèi)置的 Spider 中間件
接下來(lái),我們來(lái)看看 Scrapy 中內(nèi)置的 Spider 中間件,目前最新的 Scrapy-2.2.0 中一共有 5 個(gè)內(nèi)置的 Spider 中間件:
我們來(lái)分別介紹下這 5 個(gè)內(nèi)置的 Spider 中間件,如果能利用好這些中間件以及進(jìn)行合理的配置,可以簡(jiǎn)化不少代碼和提高網(wǎng)站爬取的成功率和效率。
DepthMiddleware
爬取深度中間件,該中間件用于追蹤被爬取網(wǎng)站中每個(gè) Request 的爬取深度。通過(guò)對(duì)該中間件相關(guān)參數(shù)的設(shè)置,可以限制爬蟲爬取的最大深度,且可以根據(jù)深度控制請(qǐng)求的優(yōu)先級(jí)等。該中間件對(duì)應(yīng)的參數(shù)設(shè)置有:
- DEPTH_LIMiT:允許爬取的最大深度,如果為0,則不限制;
- DEPTH_STATS:是否收集爬取深度統(tǒng)計(jì)數(shù)據(jù);
- DEPTH_PRIORITY:是否根據(jù) Request 深度對(duì)其安排相應(yīng)的優(yōu)先級(jí)進(jìn)行處理。
HttpErrorMiddleware
該中間件的作用是過(guò)濾掉所有不成功的 HTTP 響應(yīng),但這會(huì)增加開銷,消耗更多的資源,并使 Spider 邏輯更加復(fù)雜;
根據(jù) HTTP 標(biāo)準(zhǔn),響應(yīng)碼在 200~300 之間都是成功的響應(yīng)。如果想處理這個(gè)范圍之外的 Response,可以通過(guò) Spider 的 handle_httpstatus_list 屬性值或者配置文件中的 HTTPERROR_ALLOWED_CODES 值來(lái)指定 Spider 能處理的 Response 狀態(tài)碼:
class MySpider(CrawlSpider):
handle_httpstatus_list = [404]
例如上面這樣的寫法就是只處理響應(yīng)碼為 404 的 Response。此外 HttpErrorMiddleware 中間件的配置值有兩個(gè),分別為:
- HTTPERROR_ALLOWED_CODES:默認(rèn)值為[],傳遞此列表中包含的非200狀態(tài)代碼的所有響應(yīng);
- HTTPERROR_ALLOW_ALL:默認(rèn)為 False,傳遞所有的 Response而不考慮其狀態(tài)響應(yīng)碼的值。
OffsiteMiddleware
過(guò)濾請(qǐng)求中間件。用于過(guò)濾掉所有 Spider 覆蓋主機(jī)域名外的 URL 請(qǐng)求;
RefererMiddleware
位置參考中間件。它的作用是根據(jù)生產(chǎn)的 Response 的 URL 來(lái)填充 Request Referer 信息;
UrlLengthMiddleware
網(wǎng)址長(zhǎng)度限制中間件。它用于過(guò)濾 URL 長(zhǎng)度大于 URLLENGTH_LIMIT 的值得 Request;
在 scrapy/settings/default_settings.py
中我們可以看到這樣的配置:
SPIDER_MIDDLEWARES_BASE = {
# Engine side
'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,
# Spider side
}
這些 Spider 中間件都默認(rèn)啟用,且從 Engine 端到 Spider 端的順序如上配置所示。如果想禁止某個(gè)內(nèi)置的 Spider 中間件,我們直接在 settings.py
文件中將該 Spider 的值設(shè)置為 None 即可,示例如下:
# 配置位置: 爬蟲項(xiàng)目/settings.py
SPIDER_MIDDLEWARES = {
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None,
}
2. 下載中間件
2.1 下載中間件介紹
從前面的 Scrapy 架構(gòu)圖圖中可知,下載中間件(Downloader Middlewares)是位于 Scrapy 引擎(Scrapy Engine)和下載器(DownLoader)之間的,用于處理請(qǐng)求以及響應(yīng)的中間層。
它可以全局修改 Scrapy 的請(qǐng)求和響應(yīng)。那么如何激活下載中間件呢?假設(shè)我們編寫了一個(gè)中間件類:我們只需要將其加入全局配置文件 (settings.py
) 中即可:
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomDownloaderMiddleware': 543,
}
DOWNLOADER_MIDDLEWARES 中的 key 就是定義的下載中間件類,而 value 就是控制該中間件執(zhí)行順序的值,可以簡(jiǎn)單理解為表示該中間件執(zhí)行的優(yōu)先級(jí)。
值越小的越靠近 Scrapy 引擎,值越大越靠近下載器。我們?cè)谧远x下載中間件時(shí),也需要考慮給自定義的下載中間件設(shè)置合理的值。因此,我們先看看 Scrapy 框架默認(rèn)啟用的下載中間件情況:
# 源碼位置:scrapy/settings/default_settings.py
# ...
DOWNLOADER_MIDDLEWARES_BASE = {
# Engine side
'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,
# Downloader side
}
# ...
如何不想啟動(dòng)默認(rèn)的某個(gè)內(nèi)置中間件時(shí),同樣只需要在 settings.py
中覆蓋其值并設(shè)置為 None,即可禁用該下載中間件:
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomDownloaderMiddleware': 543,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}
了解了這些后,我們來(lái)看看如何編寫自定義的下載中間件。
2.2 編寫下載中間件
編寫自定義的下載中間件也非常簡(jiǎn)單,只需要在自定義的類中實(shí)現(xiàn)一個(gè)或多個(gè)特定名字的方法即可?,F(xiàn)在來(lái)介紹下下載中間件中那些特定方法:
process_request(request, spider)
當(dāng) Request 請(qǐng)求經(jīng)過(guò)下載中間件時(shí)會(huì)調(diào)用該方法。該方法只能返回 None、Response 對(duì)象、Request 對(duì)象或者 IgnoreRequest 異常的其中之一。如果返回 None,Scrapy 將執(zhí)行其他中間件中相應(yīng)的方法繼續(xù)處理該 Request,直到該 Request 被下載器的處理函數(shù)處理;如果返回 Response 對(duì)象,則 Scrapy 將直接返回該 Response,而不再繼續(xù)調(diào)用原鏈路上的其他中間件的 process_request(),process_exception()或相應(yīng)的下載方法,但是會(huì)依次調(diào)用已啟用的中間件的 process_request() 方法;
process_response(request, response, spider)
在請(qǐng)求的 Response 經(jīng)過(guò)下載中間件時(shí)會(huì)調(diào)用該方法。該方法返回 Response 對(duì)象、Request 對(duì)象或者是拋出 IgnoreRequest 異常。如果返回的是 Response 對(duì)象,則該 Response 會(huì)被其他中間件的 process_response()
方法處理;如果其返回的是一個(gè) Request 對(duì)象,那么其余的下載中間件將不會(huì)處理,返回的 Request 會(huì)被引擎重新調(diào)度去下載;如果是拋出異常,則調(diào)用 Request.errback。如果沒(méi)有相應(yīng)的代碼處理該異常,則忽略該異常;
process_exception(request, exception, spider)
在下載處理器或者下載中間件的 process_request()
方法拋出異常時(shí),Scrapy 將調(diào)用該方法進(jìn)行處理。該方法必須返回為 None、Response 對(duì)象以及 Request 對(duì)象三者之一;
以上這些關(guān)于下載中間件的特定函數(shù)的輸入和輸出信息我們可以在官方文檔中找到詳細(xì)的解答。上面的介紹主要是翻譯了官方文檔對(duì)這些方法的說(shuō)明。后面我們也會(huì)在學(xué)習(xí)源碼中找到這些輸出輸出的邏輯。
2.3 內(nèi)置的下載中間件
同樣,在 Scrapy 中為我們內(nèi)置了不少的下載中間件,可以方便地配置下載參數(shù),比如 Cookie、代理等。我們現(xiàn)在來(lái)介紹一些常用的下載中間件。
CookiesMiddleware:該中間件主要用于給請(qǐng)求加上 Cookie,這樣可以方便我們的爬蟲程序使用 Cookie 去訪問(wèn)網(wǎng)站。它記錄了向 Web Server 發(fā)送的 Cookie,并在之后的 Request 請(qǐng)求中帶上該 Cokkile,就像我們操作瀏覽器那樣。該中間件在 settings.py
中的配置有2個(gè):
-
COOKIES_ENABLE:默認(rèn)為 True,表明啟用 cookies 中間件,如果為 False,則不會(huì)使用 cookies。
Tips:如果
Request.meta
參數(shù)的dont_merge_cookies
的值為 True,那么無(wú)論 COOKIES_ENABLE 指定為何值,cookies 在這個(gè)請(qǐng)求的來(lái)回中都不會(huì)做任何處理; -
COOKIES_DEBUG:默認(rèn)為 False。如果為 True,則會(huì)記錄所有請(qǐng)求發(fā)送的 cookies 和響應(yīng)接收到的 cookies;
HttpProxyMiddleware:該中間件通過(guò)在 Request.meta
中添加 proxy 屬性值為該請(qǐng)求設(shè)置 HTTP 代理;
HttpCacheMiddleware:該中間件為所有 HTTP 請(qǐng)求和響應(yīng)提供 low-level 緩存,它需要和緩存存儲(chǔ)后端以及緩存的策略相結(jié)合;
DefaultHeadersMiddleware:該中間件通過(guò)配置文件中 DEFAULT_REQUEST_HEADERS
的值來(lái)設(shè)置所有請(qǐng)求默認(rèn)的請(qǐng)求頭;
DownloadTimeoutMiddleware:該中間件主要用來(lái)設(shè)置下載超時(shí)時(shí)間。對(duì)應(yīng) settings.py 中的配置值為:DOWNLOAD_TIMEOUT
或者 spider 的 download_timeout 屬性
好了,常用的下載中間件就介紹這么多了,其余的可以繼續(xù)參考官方文檔,寫的非常詳細(xì)。
3. 小結(jié)
本小節(jié)中分別從三個(gè)角度介紹了 Scrapy 中的 Spider 中間件和 下載中間件:中間件介紹、如何編寫相應(yīng)的中間件以及 Scrapy 內(nèi)置的相應(yīng)中間件。在理解了這些后,我們?cè)谙旅鎯晒?jié)會(huì)使用一個(gè)例子來(lái)對(duì)前面學(xué)到的知識(shí)進(jìn)行實(shí)戰(zhàn)演練,達(dá)到即學(xué)即用的目的。