Scrapy框架的使用之Downloader Middleware的用法

Downloader Middleware即下載中間件,它是處於Scrapy的Request和Response之間的處理模塊。咱們首先來看看它的架構,以下圖所示。
html

圖片

Scheduler從隊列中拿出一個Request發送給Downloader執行下載,這個過程會通過Downloader Middleware的處理。另外,當Downloader將Request下載完成獲得Response返回給Spider時會再次通過Downloader Middleware處理。git

也就是說,Downloader Middleware在整個架構中起做用的位置是如下兩個:github

  • 在Scheduler調度出隊列的Request發送給Doanloader下載以前,也就是咱們能夠在Request執行下載以前對其進行修改。ajax

  • 在下載後生成的Response發送給Spider以前,也就是咱們能夠在生成Resposne被Spider解析以前對其進行修改。cookie

Downloader Middleware的功能十分強大,修改User-Agent、處理重定向、設置代理、失敗重試、設置Cookies等功能都須要藉助它來實現。下面咱們來了解一下Downloader Middleware的詳細用法。架構

1、使用說明

須要說明的是,Scrapy其實已經提供了許多Downloader Middleware,好比負責失敗重試、自動重定向等功能的Middleware,它們被DOWNLOADER_MIDDLEWARES_BASE變量所定義。
app

DOWNLOADER_MIDDLEWARES_BASE變量的內容以下所示:dom

 
 

{
   '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,
}scrapy

這是一個字典格式,字典的鍵名是Scrapy內置的Downloader Middleware的名稱,鍵值表明了調用的優先級,優先級是一個數字,數字越小表明越靠近Scrapy引擎,數字越大表明越靠近Downloader,數字小的Downloader Middleware會被優先調用。ide

若是本身定義的Downloader Middleware要添加到項目裏,DOWNLOADER_MIDDLEWARES_BASE變量不能直接修改。Scrapy提供了另一個設置變量DOWNLOADER_MIDDLEWARES,咱們直接修改這個變量就能夠添加本身定義的Downloader Middleware,以及禁用DOWNLOADER_MIDDLEWARES_BASE裏面定義的Downloader Middleware。下面咱們具體來看看Downloader Middleware的使用方法。

2、核心方法

Scrapy內置的Downloader Middleware爲Scrapy提供了基礎的功能,但在項目實戰中咱們每每須要單獨定義Downloader Middleware。不用擔憂,這個過程很是簡單,咱們只須要實現某幾個方法便可。

每一個Downloader Middleware都定義了一個或多個方法的類,核心的方法有以下三個。

  • process_request(request, spider)

  • process_response(request, response, spider)

  • process_exception(request, exception, spider)

咱們只須要實現至少一個方法,就能夠定義一個Downloader Middleware。下面咱們來看看這三個方法的詳細用法。

1. process_request(request, spider)

Request被Scrapy引擎調度給Downloader以前,process_request()方法就會被調用,也就是在Request從隊列裏調度出來到Downloader下載執行以前,咱們均可以用process_request()方法對Request進行處理。方法的返回值必須爲None、Response對象、Request對象之一,或者拋出IgnoreRequest異常。

process_request()方法的參數有以下兩個。

  • request,是Request對象,即被處理的Request。

  • spider,是Spdier對象,即此Request對應的Spider。

返回類型不一樣,產生的效果也不一樣。下面概括一下不一樣的返回狀況。

  • 當返回是None時,Scrapy將繼續處理該Request,接着執行其餘Downloader Middleware的process_request()方法,一直到Downloader把Request執行後獲得Response才結束。這個過程其實就是修改Request的過程,不一樣的Downloader Middleware按照設置的優先級順序依次對Request進行修改,最後送至Downloader執行。

  • 當返回爲Response對象時,更低優先級的Downloader Middleware的process_request()process_exception()方法就不會被繼續調用,每一個Downloader Middleware的process_response()方法轉而被依次調用。調用完畢以後,直接將Response對象發送給Spider來處理。

  • 當返回爲Request對象時,更低優先級的Downloader Middleware的process_request()方法會中止執行。這個Request會從新放到調度隊列裏,其實它就是一個全新的Request,等待被調度。若是被Scheduler調度了,那麼全部的Downloader Middleware的process_request()方法會被從新按照順序執行。

  • 若是IgnoreRequest異常拋出,則全部的Downloader Middleware的process_exception()方法會依次執行。若是沒有一個方法處理這個異常,那麼Request的errorback()方法就會回調。若是該異常尚未被處理,那麼它便會被忽略。

2. process_response(request, response, spider)

Downloader執行Request下載以後,會獲得對應的Response。Scrapy引擎便會將Response發送給Spider進行解析。在發送以前,咱們均可以用process_response()方法來對Response進行處理。方法的返回值必須爲Request對象、Response對象之一,或者拋出IgnoreRequest異常。

process_response()方法的參數有以下三個。

  • request,是Request對象,即此Response對應的Request。

  • response,是Response對象,即此被處理的Response。

  • spider,是Spider對象,即此Response對應的Spider。

下面概括一下不一樣的返回狀況。

  • 當返回爲Request對象時,更低優先級的Downloader Middleware的process_response()方法不會繼續調用。該Request對象會從新放到調度隊列裏等待被調度,它至關於一個全新的Request。而後,該Request會被process_request()方法順次處理。

  • 當返回爲Response對象時,更低優先級的Downloader Middleware的process_response()方法會繼續調用,繼續對該Response對象進行處理。

  • 若是IgnoreRequest異常拋出,則Request的errorback()方法會回調。若是該異常尚未被處理,那麼它便會被忽略。

3. process_exception(request, exception, spider)

當Downloader或process_request()方法拋出異常時,例如拋出IgnoreRequest異常,process_exception()方法就會被調用。方法的返回值必須爲None、Response對象、Request對象之一。

process_exception()方法的參數有以下三個。

  • request,是Request對象,即產生異常的Request。

  • exception,是Exception對象,即拋出的異常。

  • spdier,是Spider對象,即Request對應的Spider。

下面概括一下不一樣的返回值。

  • 當返回爲None時,更低優先級的Downloader Middleware的process_exception()會被繼續順次調用,直到全部的方法都被調度完畢。

  • 當返回爲Response對象時,更低優先級的Downloader Middleware的process_exception()方法再也不被繼續調用,每一個Downloader Middleware的process_response()方法轉而被依次調用。

  • 當返回爲Request對象時,更低優先級的Downloader Middleware的process_exception()也再也不被繼續調用,該Request對象會從新放到調度隊列裏面等待被調度,它至關於一個全新的Request。而後,該Request又會被process_request()方法順次處理。

以上內容即是這三個方法的詳細使用邏輯。在使用它們以前,請先對這三個方法的返回值的處理狀況有一個清晰的認識。在自定義Downloader Middleware的時候,也必定要注意每一個方法的返回類型。

下面咱們用一個案例實戰來加深一下對Downloader Middleware用法的理解。

3、項目實戰

新建一個項目,命令以下所示:

scrapy startproject scrapydownloadertest

新建了一個Scrapy項目,名爲scrapydownloadertest。進入項目,新建一個Spider,命令以下所示:

scrapy genspider httpbin httpbin.org

新建了一個Spider,名爲httpbin,源代碼以下所示:

 
 

import scrapy
class HttpbinSpider(scrapy.Spider):
   name = 'httpbin'
   allowed_domains = ['httpbin.org']
   start_urls = ['http://httpbin.org/']

   def parse(self, response):
       pass

接下來咱們修改start_urls爲:[http://httpbin.org/](http://httpbin.org/)。隨後將parse()方法添加一行日誌輸出,將response變量的text屬性輸出出來,這樣咱們即可以看到Scrapy發送的Request信息了。

修改Spider內容以下所示:

 
 

import scrapy

class HttpbinSpider(scrapy.Spider):
   name = 'httpbin'
   allowed_domains = ['httpbin.org']
   start_urls = ['http://httpbin.org/get']

   def parse(self, response):
       self.logger.debug(response.text)

接下來運行此Spider,執行以下命令:

scrapy crawl httpbin

Scrapy運行結果包含Scrapy發送的Request信息,內容以下所示:

 
 

{
 "args": {},
 "headers": {
   "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
   "Accept-Encoding": "gzip,deflate,br",
   "Accept-Language": "en",
   "Connection": "close",
   "Host": "httpbin.org",
   "User-Agent": "Scrapy/1.4.0 (+http://scrapy.org)"
 
}
,
 "origin": "60.207.237.85",
 "url": "http://httpbin.org/get"
}

咱們觀察一下Headers,Scrapy發送的Request使用的User-Agent是Scrapy/1.4.0(+http://scrapy.org),這實際上是由Scrapy內置的`UserAgentMiddleware`設置的,`UserAgentMiddleware`的源碼以下所示:

 
 

from scrapy import signals

class UserAgentMiddleware(object):
   def __init__(self, user_agent='Scrapy'):
       self.user_agent = user_agent

   @classmethod
   def from_crawler(cls, crawler):
       o = cls(crawler.settings['USER_AGENT'])
       crawler.signals.connect(o.spider_opened, signal=signals.spider_opened)
       return o

   def spider_opened(self, spider):
       self.user_agent = getattr(spider, 'user_agent', self.user_agent)

   def process_request(self, request, spider):
       if self.user_agent:
           request.headers.setdefault(b'User-Agent', self.user_agent)

from_crawler()方法中,首先嚐試獲取settings裏面USER_AGENT,而後把USER_AGENT傳遞給__init__()方法進行初始化,其參數就是user_agent。若是沒有傳遞USER_AGENT參數就默認設置爲Scrapy字符串。咱們新建的項目沒有設置USER_AGENT,因此這裏的user_agent變量就是Scrapy。接下來,在process_request()方法中,將user-agent變量設置爲headers變量的一個屬性,這樣就成功設置了User-Agent。所以,User-Agent就是經過此Downloader Middleware的process_request()方法設置的。

修改請求時的User-Agent能夠有兩種方式:一是修改settings裏面的USER_AGENT變量;二是經過Downloader Middleware的process_request()方法來修改。

第一種方法很是簡單,咱們只須要在setting.py裏面加一行USER_AGENT的定義便可:

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'

通常推薦使用此方法來設置。可是若是想設置得更靈活,好比設置隨機的User-Agent,那就須要藉助Downloader Middleware了。因此接下來咱們用Downloader Middleware實現一個隨機User-Agent的設置。

在middlewares.py裏面添加一個RandomUserAgentMiddleware的類,以下所示:

 
 

import random

class RandomUserAgentMiddleware():
   def __init__(self):
       self.user_agents = [
           'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)',
           'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2',
           'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1'
       ]

   def process_request(self, request, spider):
       request.headers['User-Agent'] = random.choice(self.user_agents)

咱們首先在類的__init__()方法中定義了三個不一樣的User-Agent,並用一個列表來表示。接下來實現了process_request()方法,它有一個參數request,咱們直接修改request的屬性便可。在這裏咱們直接設置了request變量的headers屬性的User-Agent,設置內容是隨機選擇的User-Agent,這樣一個Downloader Middleware就寫好了。

不過,要使之生效咱們還須要再去調用這個Downloader Middleware。在settings.py中,將DOWNLOADER_MIDDLEWARES取消註釋,並設置成以下內容:

 
 

DOWNLOADER_MIDDLEWARES = {
  'scrapydownloadertest.middlewares.RandomUserAgentMiddleware': 543,
}

接下來咱們從新運行Spider,就能夠看到User-Agent被成功修改成列表中所定義的隨機的一個User-Agent了:

 
 

{
 "args": {},
 "headers": {
   "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
   "Accept-Encoding": "gzip,deflate,br",
   "Accept-Language": "en",
   "Connection": "close",
   "Host": "httpbin.org",
   "User-Agent": "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)"
 
}
,
 "origin": "60.207.237.85",
 "url": "http://httpbin.org/get"
}

咱們就經過實現Downloader Middleware並利用process_request()方法成功設置了隨機的User-Agent。

另外,Downloader Middleware還有process_response()方法。Downloader對Request執行下載以後會獲得Response,隨後Scrapy引擎會將Response發送回Spider進行處理。可是在Response被髮送給Spider以前,咱們一樣可使用process_response()方法對Response進行處理。好比這裏修改一下Response的狀態碼,在RandomUserAgentMiddleware添加以下代碼:

 
 

def process_response(self, request, response, spider):
   response.status = 201
   return response

咱們將response變量的status屬性修改成201,隨後將response返回,這個被修改後的Response就會被髮送到Spider。

咱們再在Spider裏面輸出修改後的狀態碼,在parse()方法中添加以下的輸出語句:

self.logger.debug('Status Code: ' + str(response.status))

從新運行以後,控制檯輸出了以下內容:

[httpbin] DEBUG: Status Code: 201

能夠發現,Response的狀態碼成功修改了。

所以要想對Response進行後處理,就能夠藉助於process_response()方法。

另外還有一個process_exception()方法,它是用來處理異常的方法。若是須要異常處理的話,咱們能夠調用此方法。不過這個方法的使用頻率相對低一些,在此不用實例演示。

4、本節代碼

本節源代碼爲:https://github.com/Python3WebSpider/ScrapyDownloaderTest。

5、結語

本節講解了Downloader Middleware的基本用法。此組件很是重要,是作異常處理和反爬處理的核心。後面咱們會在實戰中應用此組件來處理代理、Cookies等內容。

相關文章
相關標籤/搜索