徹底搞懂Scrapy的中介軟體(三)

青南發表於2019-03-03

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

爬蟲中介軟體

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

徹底搞懂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練習頁面exercise.kingname.info/exercise_mi… ,故意在爬蟲中製造一個異常,如圖12-26所示。

徹底搞懂Scrapy的中介軟體(三)

由於網站返回的只是一段普通的字串,並不是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。還是以抓取exercise.kingname.info/exercise_mi…這個練習頁的內容為例,但是這一次不進行重試,只記錄哪一頁出現了問題。先看爬蟲的程式碼,這一次在meta中把頁數帶上,如下圖所示。

徹底搞懂Scrapy的中介軟體(三)

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

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

徹底搞懂Scrapy的中介軟體(三)

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

徹底搞懂Scrapy的中介軟體(三)

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

徹底搞懂Scrapy的中介軟體(三)

啟用爬蟲中介軟體

爬蟲中介軟體的啟用方式與下載器中介軟體非常相似,在settings.py中,在下載器中介軟體配置項的上面就是爬蟲中介軟體的配置項,它預設也是被註釋了的,解除註釋,並把自定義的爬蟲中介軟體新增進去即可,如下圖所示。

徹底搞懂Scrapy的中介軟體(三)

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

徹底搞懂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爬蟲開發 從入門到實戰》完整目錄可以在京東查詢到 item.jd.com/12436581.ht…

買不買書不重要,重要的是請關注我的公眾號:未聞 Code

公眾號已經連續日更三個多月了。在接下來的很長時間裡也會連續日更。

徹底搞懂Scrapy的中介軟體(三)

相關文章