使用Python调用Spotify API获取最近播放歌曲的问题及缓存需求
问题根源先理清
你最开始用client_credentials授权模式失败,是因为这个模式是给应用本身授权,完全不关联具体Spotify用户,而user-read-recently-played是需要读取当前用户的私人数据,必须用授权码模式(Authorization Code Flow)——这也是你后来改模式能成功的原因。
解决手动输入授权码的方案:缓存刷新令牌
授权码模式在第一次授权后,会返回refresh_token——这个令牌可以用来在access_token过期时,自动获取新的access_token,不需要再手动走授权流程。我们可以把access_token、refresh_token和过期时间缓存到本地文件,下次运行时直接复用。
修改后的完整代码
import os import json import base64 from requests import post, get from datetime import datetime, timedelta client_id = "你的CLIENT_ID" client_secret = "你的CLIENT_SECRET" redirect_uri = "http://localhost:8888/callback" TOKEN_CACHE_FILE = "spotify_token_cache.json" def load_token_cache(): """从本地文件加载缓存的token信息""" if os.path.exists(TOKEN_CACHE_FILE): try: with open(TOKEN_CACHE_FILE, "r") as f: return json.load(f) except json.JSONDecodeError: # 缓存文件损坏,返回空 return {} return {} def save_token_cache(token_data): """把token信息保存到本地文件""" # 添加过期时间戳(当前时间加上有效期,默认3600秒) expires_at = datetime.now().timestamp() + token_data.get("expires_in", 3600) token_data["expires_at"] = expires_at with open(TOKEN_CACHE_FILE, "w") as f: json.dump(token_data, f, indent=2) def get_refreshed_token(refresh_token): """用refresh_token获取新的access_token""" token_url = "https://accounts.spotify.com/api/token" auth_string = f"{client_id}:{client_secret}" auth_bytes = auth_string.encode("utf-8") auth_base64 = str(base64.b64encode(auth_bytes), "utf-8") token_params = { "grant_type": "refresh_token", "refresh_token": refresh_token, } token_headers = { "Authorization": f"Basic {auth_base64}", "Content-Type": "application/x-www-form-urlencoded" } result = post(token_url, headers=token_headers, data=token_params) if result.status_code == 200: return json.loads(result.content) else: # 刷新失败,返回空 return {} def get_token(): """自动处理token的获取、刷新和缓存""" token_cache = load_token_cache() now = datetime.now().timestamp() # 检查缓存是否有效:存在access_token且未过期 if token_cache.get("access_token") and token_cache.get("expires_at", 0) > now: return token_cache["access_token"] # 尝试用refresh_token刷新 if token_cache.get("refresh_token"): refreshed_data = get_refreshed_token(token_cache["refresh_token"]) if refreshed_data.get("access_token"): # 更新缓存(如果返回新的refresh_token就替换,否则保留旧的) refreshed_data["refresh_token"] = refreshed_data.get("refresh_token", token_cache["refresh_token"]) save_token_cache(refreshed_data) return refreshed_data["access_token"] # 缓存无效或刷新失败,走手动授权流程 auth_url = "https://accounts.spotify.com/authorize" token_url = "https://accounts.spotify.com/api/token" auth_params = { "client_id": client_id, "response_type": "code", "redirect_uri": redirect_uri, "scope": "user-read-recently-played", "show_dialog": "false" # 可选:设置为true每次都显示授权弹窗,false则在已授权时自动跳过 } authorization_url = auth_url + "?" + "&".join([f"{k}={v}" for k, v in auth_params.items()]) print("请访问以下URL授权应用:") print(authorization_url) authorization_code = input("输入URL中的授权码:") token_params = { "grant_type": "authorization_code", "code": authorization_code, "redirect_uri": redirect_uri, } auth_string = f"{client_id}:{client_secret}" auth_bytes = auth_string.encode("utf-8") auth_base64 = str(base64.b64encode(auth_bytes), "utf-8") token_headers = { "Authorization": f"Basic {auth_base64}", "Content-Type": "application/x-www-form-urlencoded" } result = post(token_url, headers=token_headers, data=token_params) if result.status_code == 200: token_data = json.loads(result.content) save_token_cache(token_data) return token_data["access_token"] else: print(f"授权失败,状态码:{result.status_code}") return None def get_auth_header(token): return {"Authorization": f"Bearer {token}"} def get_recently_played(token, start_date): url = "https://api.spotify.com/v1/me/player/recently-played" headers = get_auth_header(token) limit = 50 query = f"?limit={limit}" timestamp_ms = int(start_date.timestamp()) * 1000 query += f"&after={timestamp_ms}" query_url = url + query result = get(query_url, headers=headers) if result.status_code == 200: return json.loads(result.content)["items"] else: print(f"获取最近播放失败,状态码:{result.status_code},响应:{result.text}") return [] # 主逻辑 access_token = get_token() if access_token: start_date = datetime(2023, 9, 10) recently_played = get_recently_played(access_token, start_date) for idx, item in enumerate(recently_played): song_name = item["track"]["name"] artist_name = item["track"]["artists"][0]["name"] print(f"{idx + 1}. {song_name} - {artist_name}")
关键说明
- 第一次运行需要手动授权一次,之后会自动读取缓存的token,不需要再输入授权码
show_dialog: false参数会让Spotify在你已经授权过该应用时,自动跳转到回调URL,不需要再次确认- 缓存文件
spotify_token_cache.json会保存在脚本同目录,里面包含access_token、refresh_token和过期时间 - 如果缓存文件丢失或refresh_token失效,才会再次触发手动授权流程
内容的提问来源于stack exchange,提问作者Physics_Student
相关产品推荐
相关产品推荐

