Tenacity——Exception Retry 从此无比简单

栏目: Python · 发布时间: 8年前

内容简介:Tenacity——Exception Retry 从此无比简单

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 ,最优雅的第三方库。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

重构

重构

Martin Fowler / 熊节 / 人民邮电出版社 / 2010 / 69.00元

重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。多年前,正是本书原版的出版,使重构终于从编程高手们的小圈子走出,成为众多普通程序员日常开发工作中不可或缺的一部分。本书也因此成为与《设计模式》齐名的经典著作,被译为中、德、俄、日等众多语言,在世界范围内畅销不衰。 本书凝聚了软件开发社区专家多年摸索而获得的宝贵经验,拥有不因时光流逝而磨灭的价值。今天,无论是重构本身,业界对重......一起来看看 《重构》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具