徹底搞懂Scrapy的中間件(三)

在前面兩篇文章介紹了下載器中間件的使用,這篇文章將會介紹爬蟲中間件(Spider Middleware)的使用。

爬蟲中間件

爬蟲中間件的用法與下載器中間件非常相似,只是它們的作用對象不同。下載器中間件的作用對象是請求request和返回response;爬蟲中間件的作用對象是爬蟲,更具體地來說,就是寫在spiders文件夾下面的各個文件。它們的關係,在Scrapy的數據流圖上可以很好地區分開來,如下圖所示。

其中,4、5表示下載器中間件,6、7表示爬蟲中間件。爬蟲中間件會在以下幾種情況被調用。

  1. 當運行到yield scrapy.Request()或者yield item的時候,爬蟲中間件的process_spider_output()方法被調用。
  2. 當爬蟲本身的代碼出現了Exception的時候,爬蟲中間件的process_spider_exception()方法被調用。
  3. 當爬蟲裏面的某一個回調函數parse_xxx()被調用之前,爬蟲中間件的process_spider_input()方法被調用。
  4. 當運行到start_requests()的時候,爬蟲中間件的process_start_requests()方法被調用。

在中間件處理爬蟲本身的異常

在爬蟲中間件裏面可以處理爬蟲本身的異常。例如編寫一個爬蟲,爬取UA練習頁面http://exercise.kingname.info/exercise_middleware_ua ,故意在爬蟲中製造一個異常,如圖12-26所示。

由於網站返回的只是一段普通的字符串,並不是JSON格式的字符串,因此使用JSON去解析,就一定會導致報錯。這種報錯和下載器中間件裏面遇到的報錯不一樣。下載器中間件裏面的報錯一般是由於外部原因引起的,和代碼層面無關。而現在的這種報錯是由於代碼本身的問題導致的,是代碼寫得不夠周全引起的。

爲了解決這個問題,除了仔細檢查代碼、考慮各種情況外,還可以通過開發爬蟲中間件來跳過或者處理這種報錯。在middlewares.py中編寫一個類:

class ExceptionCheckSpider(object):

    def process_spider_exception(self, response, exception, spider):
        print(f'返回的內容是:{response.body.decode()}\n報錯原因:{type(exception)}')
        return None

這個類僅僅起到記錄Log的作用。在使用JSON解析網站返回內容出錯的時候,將網站返回的內容打印出來。

process_spider_exception()這個方法,它可以返回None,也可以運行yield item語句或者像爬蟲的代碼一樣,使用yield scrapy.Request()發起新的請求。如果運行了yield item或者yield scrapy.Request(),程序就會繞過爬蟲裏面原有的代碼。

例如,對於有異常的請求,不需要進行重試,但是需要記錄是哪一個請求出現了異常,此時就可以在爬蟲中間件裏面檢測異常,然後生成一個只包含標記的item。還是以抓取http://exercise.kingname.info/exercise_middleware_retry.html這個練習頁的內容爲例,但是這一次不進行重試,只記錄哪一頁出現了問題。先看爬蟲的代碼,這一次在meta中把頁數帶上,如下圖所示。

爬蟲裏面如果發現了參數錯誤,就使用raise這個關鍵字人工拋出一個自定義的異常。在實際爬蟲開發中,讀者也可以在某些地方故意不使用try ... except捕獲異常,而是讓異常直接拋出。例如XPath匹配處理的結果,直接讀裏面的值,不用先判斷列表是否爲空。這樣如果列表爲空,就會被拋出一個IndexError,於是就能讓爬蟲的流程進入到爬蟲中間件的process_spider_exception()中。

在items.py裏面創建了一個ErrorItem來記錄哪一頁出現了問題,如下圖所示。

接下來,在爬蟲中間件中將出錯的頁面和當前時間存放到ErrorItem裏面,並提交給pipeline,保存到MongoDB中,如下圖所示。

這樣就實現了記錄錯誤頁數的功能,方便在後面對錯誤原因進行分析。由於這裏會把item提交給pipeline,所以不要忘記在settings.py裏面打開pipeline,並配置好MongoDB。儲存錯誤頁數到MongoDB的代碼如下圖所示。

激活爬蟲中間件

爬蟲中間件的激活方式與下載器中間件非常相似,在settings.py中,在下載器中間件配置項的上面就是爬蟲中間件的配置項,它默認也是被註釋了的,解除註釋,並把自定義的爬蟲中間件添加進去即可,如下圖所示。

Scrapy也有幾個自帶的爬蟲中間件,它們的名字和順序如下圖所示。

下載器中間件的數字越小越接近Scrapy引擎,數字越大越接近爬蟲。如果不能確定自己的自定義中間件應該靠近哪個方向,那麼就在500~700之間選擇最爲妥當。

爬蟲中間件輸入/輸出

在爬蟲中間件裏面還有兩個不太常用的方法,分別爲process_spider_input(response, spider)process_spider_output(response, result, spider)。其中,process_spider_input(response, spider)在下載器中間件處理完成後,馬上要進入某個回調函數parse_xxx()前調用。process_spider_output(response, result, output)是在爬蟲運行yield item或者yield scrapy.Request()的時候調用。在這個方法處理完成以後,數據如果是item,就會被交給pipeline;如果是請求,就會被交給調度器,然後下載器中間件纔會開始運行。所以在這個方法裏面可以進一步對item或者請求做一些修改。這個方法的參數result就是爬蟲爬出來的item或者scrapy.Request()。由於yield得到的是一個生成器,生成器是可以迭代的,所以result也是可以迭代的,可以使用for循環來把它展開。

def process_spider_output(response, result, spider):
    for item in result:
        if isinstance(item, scrapy.Item):
            # 這裏可以對即將被提交給pipeline的item進行各種操作
            print(f'item將會被提交給pipeline')
        yield item

或者對請求進行監控和修改:

def process_spider_output(response, result, spider):
    for request in result:
        if not isinstance(request, scrapy.Item):
            # 這裏可以對請求進行各種修改
            print('現在還可以對請求對象進行修改。。。。')
        request.meta['request_start_time'] = time.time()
        yield request

本文節選自我的新書《Python爬蟲開發 從入門到實戰》完整目錄可以在京東查詢到 https://item.jd.com/12436581.html

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