Tenacity——Exception Retry 從此無比簡單

Python 裝飾器裝飾類中的方法這篇文章,使用了裝飾器來捕獲代碼異常。這種方式可以讓代碼變得更加簡潔和Pythonic。

在寫代碼的過程中,處理異常并重試是一個(gè)非常常見的需求。但是如何把捕獲異常并重試寫得簡潔高效,這就是一個(gè)技術(shù)活了。

以爬蟲開發(fā)為例,由于網(wǎng)頁返回的源代碼有各種不同的情況,因此捕獲異常并重試是很常見的要求。下面這幾段代碼是我多年以前,在剛開始學(xué)習(xí)爬蟲的時(shí)候,由于捕獲異常并重試導(dǎo)致代碼混亂化過程。

代碼一開始的邏輯非常簡單,獲取網(wǎng)頁后臺(tái)API返回的JSON字符串,轉(zhuǎn)化成字典,提取出里面data的數(shù)據(jù),然后傳遞給save()函數(shù):

def extract(url):
    info_json = requests.get(url).content.decode()
    info_dict = json.loads(info_json)
    data = info_dict['data']
    save(data)

代碼運(yùn)行一段時(shí)間,發(fā)現(xiàn)有時(shí)候JSON會(huì)隨機(jī)出現(xiàn)解析錯(cuò)誤。于是添加捕獲異常并重試的功能:

def extract(url):
    info_json = requests.get(url).text
    try:
        info_dict = json.loads(info_json)
    except Exception:
        print('網(wǎng)頁返回的不是有效的JSON格式字符串,重試!')
        extract(url)
        return
    data = info_dict['data']
    save(data)

后來又發(fā)現(xiàn),有部份的URL會(huì)導(dǎo)致遞歸深度超過最大值。這是因?yàn)橛幸恍︰RL返回的是數(shù)據(jù)始終是錯(cuò)誤的,而有些URL,重試幾次又能返回正常的JSON數(shù)據(jù),于是限制只重試3次:

def extract(url):
    info_json = requests.get(url).text
    try:
        info_dict = json.loads(info_json)
    except Exception:
        print('網(wǎng)頁返回的不是有效的JSON格式字符串,重試!')
        for i in range(3):
            if extract(url):
                break

    data = info_dict['data']
    save(data)
    return True

后來又發(fā)現(xiàn),不能立刻重試,重試要有時(shí)間間隔,并且時(shí)間間隔逐次增大......

從上面的例子中可以看到,對(duì)于異常的捕獲和處理,一不小心就讓整個(gè)代碼變得很難看很難維護(hù)。為了解決這個(gè)問題,就需要通過裝飾器來完成處理異常并重試的功能。

Python 有一個(gè)第三方庫,叫做Tenacity,它實(shí)現(xiàn)了一種優(yōu)雅的重試功能。

以上面爬蟲最初的無限重試版本為例,如果想實(shí)現(xiàn)遇到異常就重試。只需要添加兩行代碼,爬蟲的主體函數(shù)完全不需要做修改:

from tenacity import retry

@retry
def extract(url):
    info_json = requests.get(url).content.decode()
    info_dict = json.loads(info_json)
    data = info_dict['data']
    save(data)

現(xiàn)在要限制重試次數(shù)為3次,代碼總行數(shù)不需要新增一行就能實(shí)現(xiàn):

from tenacity import retry

@retry(stop=stop_after_attempt(3))
def extract(url):
    info_json = requests.get(url).content.decode()
    info_dict = json.loads(info_json)
    data = info_dict['data']
    save(data)

現(xiàn)在想每5秒鐘重試一次,代碼行數(shù)也不需要增加:

from tenacity import retry

@retry(wait=wait_fixed(5))
def extract(url):
    info_json = requests.get(url).content.decode()
    info_dict = json.loads(info_json)
    data = info_dict['data']
    save(data)

甚至重試的時(shí)間間隔想指數(shù)級(jí)遞增,代碼行數(shù)也不需要增加:

from tenacity import retry

@retry(wait=wait_exponential(multiplier=1, max=10)) # 重試時(shí)間間隔滿足:2^n * multiplier, n為重試次數(shù),但最多間隔10秒
def extract(url):
    info_json = requests.get(url).content.decode()
    info_dict = json.loads(info_json)
    data = info_dict['data']
    save(data)

重試不僅可以限制次數(shù)和間隔時(shí)間,還可以針對(duì)特定的異常進(jìn)行重試。在爬蟲主體中,其實(shí)有三個(gè)地方可能出現(xiàn)異常:

  • requests獲取網(wǎng)頁出錯(cuò)
  • 解析JSON出錯(cuò)
  • info_dict字典里面沒有data這個(gè)key

如果只需要在JSON解析錯(cuò)誤時(shí)重試,由于異常類型為json.decoder.JSONDecodeError,所以就可以通過參數(shù)來進(jìn)行限制:

from tenacity import retry
from json.decoder import JSONDecodeError

@retry(retry=retry_if_exception_type(JSONDecodeError))
def extract(url):
    info_json = requests.get(url).content.decode()
    info_dict = json.loads(info_json)
    data = info_dict['data']
    save(data)

當(dāng)然,這些特性都可以進(jìn)行組合,例如只對(duì)JSONDecodeError 進(jìn)行重試,每次間隔5秒,重試三次,那就寫成:

from tenacity import retry
from json.decoder import JSONDecodeError

@retry(retry=retry_if_exception_type(JSONDecodeError), wait=wait_fixed(5), stop=stop_after_attempt(3))
def extract(url):
    info_json = requests.get(url).content.decode()
    info_dict = json.loads(info_json)
    data = info_dict['data']
    save(data)

自始至終,爬蟲主體的代碼完全不需要做任何修改。

Tenacity是我見過的,最 Pythonic ,最優(yōu)雅的第三方庫。

** 本文首發(fā)地址:https://kingname.info/2017/06/18/easy-retry/** 轉(zhuǎn)載請(qǐng)注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,034評(píng)論 25 709
  • 只要不怕麻煩,做想做的事,其實(shí)沒那么難。
    執(zhí)念馥郁閱讀 188評(píng)論 0 0
  • 文/寒霜 【原創(chuàng)】 江南春·桃花 青淺淺,粉嫣嫣。 枝飛嬌媚鮮,朝落彩霞丹。 ...
    劉寒霜閱讀 492評(píng)論 36 28
  • 聽一首簡單的歌, 寫一句簡單的話, 望一輪皎潔的明月, 夜深了,心情安靜了。 想一段似水的記憶, 夢一場自導(dǎo)的話劇...
    Cinko閱讀 251評(píng)論 0 17

友情鏈接更多精彩內(nèi)容