Tenacity——Exception Retry 从此无比简单

Python 装饰器装饰类中的方法这篇文章,使用了装饰器来捕获代码异常。这种方式可以让代码变得更加简洁和Pythonic。

在写代码的过程中,处理异常并重试是一个非常常见的需求。但是如何把捕获异常并重试写得简洁高效,这就是一个技术活了。

<!--more-->

以爬虫开发为例,由于网页返回的源代码有各种不同的情况,因此捕获异常并重试是很常见的要求。下面这几段代码是我多年以前,在刚开始学习爬虫的时候,由于捕获异常并重试导致代码混乱化过程。

代码一开始的逻辑非常简单,获取网页后台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, stop_after_attempt

@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, wait_fixed

@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, wait_exponential

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

欢迎关注我的公众号:未闻Code(ID:itskingname)

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Crossin的编程教室

一键下载:将知乎专栏导出成电子书

老是有同学问,学了 Python 基础后不知道可以做点什么来提高。今天就再用个小例子,给大家讲讲,通过 Python 和爬虫,可以完成怎样的小工具。

46710
来自专栏一个会写诗的程序员的博客

Bruce Eckel:编程生涯

作者 Bruce Eckel 是编程界的大牛,著有大名鼎鼎的《Thinking in C++》和《Thinking in Java》。 本文是他对程序员(尤其...

10620
来自专栏漏斗社区

专属| GitHub发布Python安全警告

近日,GitHub宣布了Python安全警告,使Python用户可以访问依赖图,并在他们的库所依赖的包存在安全漏洞时收到警告。GitHub会根据MITRE的公共...

20220
来自专栏数据科学与人工智能

【教程】机器学习Python教程:2机器学习术语

这意味着分类器在42个案例中正确地预测了为男性,并错误地预测了8个男性案例为女性。它正确地预测了32例女性,18例被错误地预测为男性而不是女性。

12420
来自专栏java工会

Java 动态代理及 RPC 框架介绍

所谓动态代理,指的是语言提供的一种语法,能够将对对象中不同方法的调用重定向到一个统一的处理函数中来。 python重写__getattr__函数能够做到这一点,...

16210
来自专栏恰童鞋骚年

Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作

  首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl、PHP为主。 仅仅memcached网站上列出的语言就有:Perl、PHP、...

15730
来自专栏ionic3+

《跳一跳》小程序,python作弊ios版细节说明

功能不止作弊这么玩,其实很强大的,如常用于手机应用自动化测试,具体内容可以参考下文: 基于WebDriverAgent代理服务,实现iOS手机app自动化测试...

19840
来自专栏数据科学与人工智能

【教程】机器学习Python教程:1机器学习

机器学习是一种编程,它使计算机能够在没有显式编程的情况下自动地从数据中学习。换句话说,这意味着这些程序通过学习数据来改变它们的行为。

14020
来自专栏ionic3+

【Go回忆】Go,小众型的开发语言么?但我建议你学一下

当你掌握了一门基本的看家技术,如java、C#、php,swift或者js等语言及相关配套技术后,当你有业余时间时,我觉得可以结合自身的兴趣和技术背景,来选择学...

15230
来自专栏Crossin的编程教室

不懂就问,这波虎扑diss吴亦凡属于什么水平?

(押韵支持来自我们去年的文章 Python有嘻哈:Crossin教你用代码写出押韵的verse)

10630

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励