完全搞懂scrapy的中間件第三章

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

爬蟲中間件

爬蟲中間件的用法與下載器中間件很是類似,只是它們的做用對象不一樣。下載器中間件的做用對象是請求request和返回response;爬蟲中間件的做用對象是爬蟲,更具體地來講,就是寫在spiders文件夾下面的各個文件。它們的關係,在Scrapy的數據流圖上能夠很好地區分開來,以下圖所示。python

其中,四、5表示下載器中間件,六、7表示爬蟲中間件。爬蟲中間件會在如下幾種狀況被調用。markdown

  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所示。scrapy

因爲網站返回的只是一段普通的字符串,並非JSON格式的字符串,所以使用JSON去解析,就必定會致使報錯。這種報錯和下載器中間件裏面遇到的報錯不同。下載器中間件裏面的報錯通常是因爲外部緣由引發的,和代碼層面無關。而如今的這種報錯是因爲代碼自己的問題致使的,是代碼寫得不夠周全引發的。ide

爲了解決這個問題,除了仔細檢查代碼、考慮各類狀況外,還能夠經過開發爬蟲中間件來跳過或者處理這種報錯。在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解析網站返回內容出錯的時候,將網站返回的內容打印出來。post

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

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

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

在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
相關文章
相關標籤/搜索