「生活可以更简单, 欢迎来到我的开源世界」
  1. 简介
  2. 用法
  3. 其他
python爬虫——一个通用的retry库:Tenacity
2019-10-10

一个健壮的爬虫程序需要捕获并处理程序在运行过程中的异常,其中网络连接相关的错误是很令人烦恼的,出现网络连接问题的原因是多样的,很多时候无法明确判断是不是程序真的无法执行下去了,从而重试操作是最先考虑的方法。Tenacity库提供重试装饰器,很是方便。

原文链接

简介

Tenacity1是一个通用的retry库,简化为任何任务加入重试的功能。

它还包含如下特性:

用法

  1. 简单用法

    from tenacity import *

    # 基础的用法,会一直重试下去,直到函数没有抛出异常,正常返回值
    @retry
    def never_give_up_never_surrender():
    print("一直重试,忽略exceptions,重试间没有等待时间")
    raise Exception
  2. 设置停止条件

    在达到尝试次数后停下来:

    @retry(stop=stop_after_attempt(7))
    def stop_after_7_attempts():
    print("尝试7次后停下")
    raise Exception

    10秒后仍然没有成功则停下:

    @retry(stop=stop_after_delay(10))
    def stop_after_10_s():
    print("10秒后停止")
    raise Exception

    使用|操作符组合多种条件:

    @retry(stop=(stop_after_delay(10) | stop_after_attempt(5)))
    def stop_after_10_s_or_5_retries():
    print("10秒后,或者尝试5次后,停下来")
    raise Exception
  3. 设置重试间隔

    @retry(wait=wait_fixed(2))
    def wait_2_s():
    print("每次重试间都有2秒间隔")
    raise Exception

    随机间隔时间:

    @retry(wait=wait_random(min=1, max=2))
    def wait_random_1_to_2_s():
    print("重试间隔1-2秒")
    raise Exception

    指数曲线间隔时间:

    @retry(wait=wait_exponential(multiplier=1, min=4, max=10))
    def wait_exponential_1():
    print("开始的时候等待 2^x * 1 秒,最少等待4秒,最多10秒,之后都是等待10秒")
    raise Exception

    指数间隔时间:

    多核在竞争一个共享的资源,使用指数间隔可以将冲突最小化

    @retry(wait=wait_random_exponential(multiplier=1, max=60))
    def wait_exponential_jitter():
    print("随机等待 2^x * 1 秒,最多60秒,之后都是等待60秒")
    raise Exception

    可以自定义每次等待时长:

    ```
    @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] +
    [wait_fixed(7) for i in range(2)] +
    [wait_fixed(9)]))
    def wait_fixed_chained():
    print("前三次等待3秒,后两次等待7秒,最后一次等待9秒")
    raise Exception
    ```
  4. 设置retry条件

    默认情况下,只有函数抛出异常时才会retry。

    可以设置在制定的异常才进行retry:

    @retry(retry=retry_if_exception_type(IOError))
    def might_io_error():
    print("只有在IOError的时候进行retry,其它时候照常抛出错误")
    raise Exception

    可以在判断返回值是否是需要的情况下进行retry:

    def is_none_p(value):
    return value is None

    @retry(retry=retry_if_result(is_none_p))
    def might_return_none():
    print("因为返回值是None,所以这个函数会一直retry")

    # 这样写也是可以的,不用修改原来的代码
    retry_version_func = retry(retry=retry_if_result(is_none_p))(might_return_none)

    组合多个条件:

    def is_none_p(value):
    return value is None

    @retry(retry=(retry_if_result(is_none_p) | retry_if_exception_type()))
    def might_return_none():
    print("在抛出任何异常,或者返回值是None的情况下,进行retry")

其他

在函数体内,可以手动抛出TryAgain错误,然后进行重试:

@retry
def do_something():
result = something_else()
if result == 23:
raise TryAgain

通过参数reraise=True,可以抛出函数最后一次抛出的异常。如果没有设定,会抛出RetryError:

@retry(reraise=True, stop=stop_after_attempt(3))
def raise_my_exception():
raise MyException("Fail")

try:
raise_my_exception()
except MyException:
print('MyException会被抛出')

在重试的前后,记录日志:

import logging

logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

logger = logging.getLogger(__name__)

# 重试前记录
@retry(stop=stop_after_attempt(3), before=before_log(logger, logging.DEBUG))
def raise_my_exception():
raise MyException("Fail")

# 重试后记录
@retry(stop=stop_after_attempt(3), after=after_log(logger, logging.DEBUG))
def raise_my_exception():
raise MyException("Fail")

获取retry的相关统计数据:

@retry(stop=stop_after_attempt(3))
def raise_my_exception():
raise MyException("Fail")

try:
raise_my_exception()
except Exception:
pass

print(raise_my_exception.retry.statistics)
<⇧>