Python 裝飾器裝飾類中的方法這篇文章,使用了裝飾器來捕獲程式碼異常。這種方式可以讓程式碼變得更加簡潔和Pythonic。
在寫程式碼的過程中,處理異常並重試是一個非常常見的需求。但是如何把捕獲異常並重試寫得簡潔高效,這就是一個技術活了。
以爬蟲開發為例,由於網頁返回的原始碼有各種不同的情況,因此捕獲異常並重試是很常見的要求。下面這幾段程式碼是我多年以前,在剛開始學習爬蟲的時候,由於捕獲異常並重試導致程式碼混亂化過程。
程式碼一開始的邏輯非常簡單,獲取網頁後臺API返回的JSON字串,轉化成字典,提取出裡面data
的資料,然後傳遞給save()
函式:
def extract(url):
info_json = requests.get(url).content.decode()
info_dict = json.loads(info_json)
data = info_dict[`data`]
save(data)複製程式碼
程式碼執行一段時間,發現有時候JSON會隨機出現解析錯誤。於是新增捕獲異常並重試的功能:
def extract(url):
info_json = requests.get(url).text
try:
info_dict = json.loads(info_json)
except Exception:
print(`網頁返回的不是有效的JSON格式字串,重試!`)
extract(url)
return
data = info_dict[`data`]
save(data)複製程式碼
後來又發現,有部份的URL會導致遞迴深度超過最大值。這是因為有一些URL返回的是資料始終是錯誤的,而有些URL,重試幾次又能返回正常的JSON資料,於是限制只重試3次:
def extract(url):
info_json = requests.get(url).text
try:
info_dict = json.loads(info_json)
except Exception:
print(`網頁返回的不是有效的JSON格式字串,重試!`)
for i in range(3):
if extract(url):
break
data = info_dict[`data`]
save(data)
return True複製程式碼
後來又發現,不能立刻重試,重試要有時間間隔,並且時間間隔逐次增大……
從上面的例子中可以看到,對於異常的捕獲和處理,一不小心就讓整個程式碼變得很難看很難維護。為了解決這個問題,就需要通過裝飾器來完成處理異常並重試的功能。
Python 有一個第三方庫,叫做Tenacity,它實現了一種優雅的重試功能。
以上面爬蟲最初的無限重試版本為例,如果想實現遇到異常就重試。只需要新增兩行程式碼,爬蟲的主體函式完全不需要做修改:
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)複製程式碼
現在要限制重試次數為3次,程式碼總行數不需要新增一行就能實現:
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)複製程式碼
現在想每5秒鐘重試一次,程式碼行數也不需要增加:
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)複製程式碼
甚至重試的時間間隔想指數級遞增,程式碼行數也不需要增加:
from tenacity import retry
@retry(wait=wait_exponential(multiplier=1, max=10)) # 重試時間間隔滿足:2^n * multiplier, n為重試次數,但最多間隔10秒
def extract(url):
info_json = requests.get(url).content.decode()
info_dict = json.loads(info_json)
data = info_dict[`data`]
save(data)複製程式碼
重試不僅可以限制次數和間隔時間,還可以針對特定的異常進行重試。在爬蟲主體中,其實有三個地方可能出現異常:
- requests獲取網頁出錯
- 解析JSON出錯
- info_dict字典裡面沒有
data
這個key
如果只需要在JSON解析錯誤時重試,由於異常型別為json.decoder.JSONDecodeError
,所以就可以通過引數來進行限制:
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)複製程式碼
當然,這些特性都可以進行組合,例如只對JSONDecodeError
進行重試,每次間隔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 ,最優雅的第三方庫。
本文首發地址:kingname.info/2017/06/18/…轉載請註明出處。