scrapy-redis(二)

在scrapy中幫助我們進行一些預處理的組件稱之爲middleware。比如將request對象交給下載器下載之前,就會通過下載中間件的process_request()方法。這些中間件的用處非常大,我們可以詳細的瞭解,以方便編寫自己的中間件。

1.spider-middleware
該中間件位於spider和engine之間,按照我們給出的數字依次從小到大,順序執行。數字越小的中間件越靠近engine,數字越大的中間件越靠近spider.

scrapy默認給出了幾個中間件,分別是:

'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50,
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500,
'scrapy.spidermiddlewares.referer.RefererMiddleware': 550,
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800,
'scrapy.spidermiddlewares.depth.DepthMiddleware': 900,

默認情況下這些中間件都是啓用的,當然我們也可以編寫自己的中間件並加入到上面的字典中。
A.HttpErrorMiddleware:指的是過濾出我們希望spider不用去處理的response,比如狀態碼爲404的。默認情況下,spider會處理所有的response,但我們也可以加一些配置,在settings.py這個文件中做如下配置:

HTTPERROR_ALLOWED_CODES = [200, 301, 302, 500, 503]

這樣如果response不在列表中,那麼該response就會被過濾掉,spider就不會做任何處理。但我個人的想法是,應該禁用這個默認的中間件,原因在介紹下載中間件的時候會詳細介紹。

B.RefererMiddleware:這個中間件是給request對象添加referer頭部的,照理來說,這個中間件是非常有用的。但是,有可能會出現這樣一種情況,在不同的頁面中,會出現相同的url,而這個url又符合我們抓取的規則,由於是在不同的頁面,所以會分別創建request對象,然後經過這個中間件的時候,就會加上一個referer頭部,這樣這兩個request對象就不一樣了,實際上指向的是同一個url。然後在調度器中生成指紋對象的時候,這兩個request對象的指紋是不一樣的,因爲期間有一個referer頭部是不一樣的。所以這兩個request對象就會分別進行抓取,這就照成了資源浪費。所以建議還是禁用這個中間件。當然,如果你能修改scrapy去重request對象的時間點,那就更好了。
更改:以上說明有誤,在將request對象轉換成指紋的時候,是可以決定是否去除headers頭部的

C.UrlLengthMiddleware:這表示了我們希望抓取的url的最大長度,看個人需求了,如果你只想抓取長度爲100以下的url,在settings.py中作如下配置即可:

URLLENGTH_LIMIT = 100

D.DepthMiddleware:表示的是抓取的最大深度,我個人覺得這個沒啥必要,因爲url就是從頁面上點擊的,難道點擊了5次之後的頁面就不重要了嗎?
可通過如下設置自定義這個中間件的一些狀態:

DEPTH_LIMIT = 5
DEPTH_STATS = True
DEPTH_PRIORITY = False

E.OffsiteMiddleware:我們在定義spider的時候,會定義allowed_domains這個數據屬性,所以這個中間件就是爲我們過濾出不在allowed_domains中的站點以及相關的url,非常的實用。
注意,如果request對象的dont_filter屬性設置爲True,那麼這個中間件就沒用了。

如果我們要自定義自己的spider中間件,也非常的簡單:

class MyCustomSpiderMiddleWares(object):

    def process_spider_input(self, response, spider):
        #該方法處理spider接受到的response
        #可以在這裏將response存放到數據庫中,實現緩存
        return None

    def process_spider_output(self, response, result, spider):
        #該方法處理spider解析response的輸出,
        #所以result有可能是item或者是可迭代的requests對象
        #這裏就可以處理一些請求對象或者處理tiem
        return result

    def process_spider_exception(self, response, exception, spider):
        return None

    #這個方法必須返回一個可迭代的request對象
    #最好還是忽略這個方法
    #def  process_start_requests(self, start_requests, spider):
    #   pass

我們只要實現上述方法中的某幾個就可以了,當然也可以實現其他方法,比如from_crawler()這個類方法。
process_spider_input(self, response, spider)這個方法接受從engine傳過來的response,最終會傳給spider進行處理,比如提取links,或者items

process_spider_output(self, response, result, spider)這個方法接受spider處理response之後,傳出的數據,比如items或者links

process_spider_exception(self, response, exception, spider)這個方法處理spider或者其他中間件引發的異常

process_start_requests(self, start_requests, spider)這個方法處理start_requests中傳遞出的requests對象,建議不要實現這個方法,沒什麼必要。

所以,我們可以自定義一個spider中間件用來實現http緩存,將下載下來的網頁保存的數據庫中,比如mysql或者mongodb。當然scrapy有默認的http緩存,但好像沒有實現mysql和mongodb的。

2.doanload-middleware
該中間件位於engine和下載器之間,數據越小的越靠近engine.
scrapy也給出了默認的實現:

'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 550,
'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
'scrapy.downloadermiddlewares.chunked.ChunkedTransferMiddleware': 830,
'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': None,
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': None,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': None,   
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': None,
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': None,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': None,
'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': None,

我禁用下載超時,重定向,重試,是因爲我在request.meta裏面進行了相應的設置,另外scrapy默認的用戶代理是要禁掉的,這樣就可以準備大量的用戶代理,已被每次隨機使用。至於http緩存,我禁用了scrapy自帶的,可自行編寫一個在spider-middleware實現的http緩存。
作爲爬蟲,cookie是一定要禁掉的,這隻會增加下載延遲。除非你抓取的是登錄的頁面。

這裏重點講解一下關於處理下載器剛下載下來的response。在spidermiddleware中,我禁掉了httperror中間件,其中的原因是,我禁止了scrapy自帶下載中間件中重定向,重試以及metarefeash中間件。原因是這非常的影響爬蟲的性能,只會增加爬蟲的消耗,而不會帶來任何好處。爲什麼這麼說呢?

每一次的重定向,都有可能增加dns解析,tcp/ip鏈接,然後纔是發送http請求。我們爲什麼要浪費這麼多的時間,沒任何理由。所以我的做法是,接受任何響應,然後在下載中間件中處理這個響應,過濾出200狀態碼的相應交給engine.對於那麼重定向的(301, 302,meta-refreash等),我提取出響應頭部中的’location’等,然後重新生成一個request對象,交給調度器重新調度。對於404響應,直接拋棄。對於500+響應,把初始request對象重新交給調度器。這樣,既不會影響爬蟲的正常抓取,也不會落下需要再次抓取的request對象。

那麼對於返回的request對象,重新回到調度器,會不會被去重過濾掉呢?然後官方文檔上說的是,返回的request對象是一定會被下載的,姑且認爲不會被去重吧。

def process_response(self, request, response, spider):
        #處理下載完成的response
        #排除狀態碼不是304的所有以3爲開頭的響應
        http_code = response.status
        if http_code // 100 == 2:
            return response

        if http_code // 100 == 3 and http_code != 304:
            #獲取重定向的url
            url = response.headers['location']
            domain = urlparse.urlparse(url).netloc
            #判斷重定向的url的domain是否在allowed_domains中
            if domain in spider.allowed_domains:        
                return Request(url=url, meta=request.meta)
            else:
                raise IgnoreRequest(u'not allowed to crawl')

        if http_code // 100 == 4:
            #需要注意403不是響應錯誤,是無權訪問
            raise IgnoreRequest(u'404')

        if http_code // 100 == 5:                       
            return request

        #處理網頁meta refresh的問題        
        url = html.get_html_meta_refresh(response)
        if url:
            domain = urlparse.urlparse(url).netloc
            #判斷meta refresh重定向的url的domain是否在allowed_domains中
            if domain in spider.allowed_domains:        
                return Request(url=url, meta=request.meta)

上述就是我自定義下載中間件中處理response的方法,只返回200的響應,其他響應分別做各自的處理。

3.總結
對於中間件,我們能做的事情非常多,目前限於水平,能想到的一些方法就這點。當然,以上是我自己的一些想法,可能與你的想法完全不同,並沒有對錯之分,每個人都能找到適合自己的最優的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章