python重试 tenacity-分页情况下的重试(page参数保留)

发布时间:

https://github.com/jd/tenacity  是python生态里一个很棒的重试库,基本用法大家可去查看他们的官网

背景

要写个爬虫,要抓的数据是分页的,每次请求都带一个page参数,如果请求失败,就重试,重试的时候,page参数要保持,不能丢失

默认情况 tenacity 每次重试,都会用 函数首次传入的参数去重试, 如果我请求数据到23页,这时候报错, 它触发重试机制的话, 就会从第1页开始重试, 这没有意义

错误示范

import logging import random import requests from tenacity import retry, stop_after_attempt, wait_exponential # 设置日志配置 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def log_retry_attempt(retry_state): """记录重试信息的回调函数""" # 注意,使用重试的函数,上游调用它的时候传参必须k=v格式传参,不能用位置传参,不然kwargs里什么都拿不到 page = retry_state.kwargs.get('page') exception = retry_state.outcome.exception() logger.warning( f"正在重试 {retry_state.fn.__name__} " f"(第 {retry_state.attempt_number} 次尝试) " f"针对 page:{page}。" f"将在{retry_state.next_action.sleep} 秒后重试... " f"异常: {str(exception)}" ) # 这里重试的时候,它会用上游调用它的时候,传递的参数来重试,上游我们传递的参数page=1,所以不论尝试多少次,page都是1,这样死循环是没有意义的 @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10), before_sleep=log_retry_attempt, reraise=True ) def fetch_data(page): all_data = [] while True: try: print(f"正在请求第{page}页数据") # 模拟一个API请求,假设请求失败 response = requests.get(f'https://api.restful-api.dev/objects/{page}') response.raise_for_status() data = response.json() # 随机抛出错误 if page > 2: # 第二页以后才随机出错 if random.random() < 0.3: # 30% 概率抛出错误 raise ValueError("模拟的随机错误") all_data.extend(data) except requests.exceptions.RequestException as e: # 如果请求失败,抛出异常,重试 raise e page = page + 1 pass if __name__ == "__main__": res = fetch_data(page=1) print(res)

错误示范截图

错误示范截图 { w: 1714, h: 844, cap: "" }

正确示范

import logging import random import requests from tenacity import retry, stop_after_attempt, wait_exponential # 设置日志配置 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def log_retry_attempt(retry_state): """记录重试信息的回调函数""" # 注意,使用重试的函数,上游调用它的时候传参必须k=v格式传参,不能用位置传参,不然kwargs里什么都拿不到 kwargs = retry_state.kwargs exception = retry_state.outcome.exception() # 可以兼容分页,不分页的情况 kwargs_info = "" if kwargs: kwargs_info = f"kwargs:{kwargs}。" info = f"正在重试 {retry_state.fn.__name__} (第 {retry_state.attempt_number} 次尝试), {kwargs_info} , 将在{retry_state.next_action.sleep} 秒后重试... ,异常: {str(exception)}" logger.warning(info) # 这里重试的时候,也是用上游调用它的时候,传递的参数来重试,上游我们传递的参数page在fetch_all_data里,每一次都不一样,某次报错,就会用错误的那一轮的page重试,这样就刚好符合我们的需求 @retry( stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10), before_sleep=log_retry_attempt, reraise=True ) def fetch_data(page): print(f"正在请求第{page}页数据") # 随机抛出错误 if page > 2: # 第二页以后才随机出错 if random.random() < 0.3: # 30% 概率抛出错误 raise ValueError("模拟的随机错误") response = requests.get(f'https://api.restful-api.dev/objects/{page}') response.raise_for_status() data = response.json() return data def fetch_all_data(page): all_data = [] while True: try: data = fetch_data(page=page) # 每次调用,传递的page都不同,如果请求失败,就会用这个page重试,成功后,我们就会用新的page调用fetch_data, 完美符合需求 all_data.extend(data) page += 1 except Exception as e: print(e) break return all_data if __name__ == "__main__": res = fetch_all_data(page=1) print(res)

正确示范截图

正确示范截图 { w: 1714, h: 844, cap: "" }

总结

其实就是 获取数据 专门独立为一个函数,里面不需要什么容错语句,就是单纯粗暴的获取数据并返回,然后给这个函数头上挂重试的装饰器,这样当这个函数被上游调用出错的时候,就会用参数page重试 (比如 正确示范里的 fetch_data 方法)

而上游的方法(比如 正确示范里的 fetch_all_data 方法 ),则必须 用循环的方式, 处理好 page, 每次用不同的参数 page 去调用有重试机制的函数


2025 © 糊涂.