使用Spotipy调用Spotify API反复出现HTTP 429错误求助
问题概述
- 18小时前因请求量过大触发Spotify API速率限制,原代码执行到API调用时无限挂起
- 12小时前重写代码,设置
retries = 0以抛出错误而非挂起,同时添加请求间隔控制,但重写后仅能成功发起1次API请求,之后每次均返回HTTP 429错误,间隔数小时重试仍无法解决 - 已确保所有请求间隔至少1秒,收到
429错误时采用指数递增时间重试(最长等待2分钟),尝试更换开发者账号下两个应用的Client ID/Secret均无效 - 疑问:当前处理方式是否正确?代码存在哪些问题?如何获取429响应头的
Retry-After字段?该字段能否解决问题?
代码问题分析
1. 请求时间更新逻辑缺失
成功发起API请求后,未更新self.recent_request为当前时间,导致后续请求的间隔计算完全错误,可能短时间内连续发起请求触发速率限制。
2. 递归分支无返回值
当请求间隔不足1秒时,递归调用get_playlist_tracks但未返回结果,会导致上层调用拿到None,进而引发后续代码报错,同时也会导致请求时间无法正确更新。
3. Retry-After字段获取方式错误
Spotipy的SpotifyException中,响应头信息存储在e.response.headers而非e.headers中,直接访问e.headers无法获取到Retry-After字段。
4. 重试逻辑未结合请求间隔
重试时直接发起请求,未检查当前时间与上一次请求的间隔,可能刚完成等待就再次触发速率限制。
5. 未更新offset导致无限重复请求
原代码中循环获取 playlist 数据时,未递增offset值,会无限请求同一页数据,进一步加剧速率限制问题。
修复建议与解决方案
1. 修复请求间隔与时间更新逻辑
确保每次发起请求前检查间隔,且成功请求后更新请求时间,避免短时间内连续请求。
2. 正确获取Retry-After字段
使用e.response.headers.get('Retry-After')获取官方建议的等待时间,优先遵循该时间而非自定义的指数退避时间,这能更精准地匹配Spotify的限制规则。
3. 完善递归重试的返回逻辑
所有递归分支必须返回结果,避免出现None值导致后续流程中断。
4. 排查账号/应用状态
若间隔数小时仍返回429,可能是Spotify对账号或应用进行了临时的严格限制,需检查开发者控制台的应用状态,确认是否有额外限制通知。
修复后的代码示例
import spotipy # type: ignore from spotipy.oauth2 import SpotifyOAuth # type: ignore import time def create_client() -> spotipy.Spotify: sp: spotipy.Spotify = spotipy.Spotify( retries=0, auth_manager=SpotifyOAuth( client_id='my_id', client_secret='_my_secret', scope='playlist-read-private playlist-modify-public playlist-modify-private', redirect_uri='http://localhost:8888/callback' ) ) return sp class APIRequests: def __init__(self, user_id: str) -> None: self.recent_request: float = time.time() self.sp: spotipy.Spotify = create_client() self.user_id: str = user_id def get_playlist_tracks(self, playlist: str, offset: int, retries: int=0): # 确保两次请求间隔至少1秒 delta_time = time.time() - self.recent_request if delta_time < 1: time.sleep(1 - delta_time) try: # 更新请求时间为当前发起请求的时间 self.recent_request = time.time() response = self.sp.user_playlist_tracks(self.user_id, playlist, offset=offset, limit=100) return response except spotipy.exceptions.SpotifyException as e: if e.http_status == 429: # 正确获取Retry-After字段 retry_after = e.response.headers.get('Retry-After') # 优先使用官方建议的等待时间,否则用指数退避 wait_time = int(retry_after) if retry_after else (2 ** retries) * 4 print(f"收到HTTP 429错误,{'官方建议等待'+retry_after+'秒' if retry_after else f'等待{wait_time}秒'}") time.sleep(wait_time) retries += 1 return self.get_playlist_tracks(playlist, offset, retries) else: raise e api_client: APIRequests = APIRequests('my_user_id') def get_playlist_tracks(playlist: str) -> list: offset: int = 0 tracks: list = [] while True: print('发送请求...') request_batch = api_client.get_playlist_tracks(playlist, offset) print('请求完成') for track in request_batch['items']: tracks.append(track['track']) if not request_batch["next"]: break offset += 100 # 递增offset获取下一页数据 return tracks if __name__ == '__main__': tracks = get_playlist_tracks('my_playlist') print(len(tracks))
关于Retry-After字段的作用
Spotify返回的Retry-After字段会明确告知你需要等待的秒数,遵循该时间等待能确保你在合规的时间点重新发起请求,比自定义的指数退避更精准,能有效避免重复触发429错误。
内容的提问来源于stack exchange,提问作者flakpm

