You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 15:49:51