如何在Docker环境中无浏览器、不使用Flask运行Spotipy脚本
解决Docker中Spotipy无头认证与无输出问题
问题核心
- 无头认证失败:Spotipy默认OAuth流程依赖浏览器交互,Docker无头环境无法触发该流程,导致脚本卡在认证环节。
- 无控制台输出:Python默认缓冲标准输出,加上脚本可能卡在认证步骤,导致容器启动后无任何反馈。
解决方案
一、适配Spotipy无头环境认证
采用离线授权模式,提前在本地完成一次认证获取refresh token,后续通过该token自动刷新access token,无需浏览器交互:
本地预生成授权缓存
在本地(非Docker环境)运行原脚本,完成浏览器认证流程,生成.cache文件(包含refresh token等敏感信息)。修改认证逻辑
调整refresh_access_token函数,禁用浏览器启动,复用缓存文件自动刷新token:
def refresh_access_token(): auth_manager = SpotifyOAuth( client_id="<foo>", client_secret="<foo>", redirect_uri="http://localhost:8888", scope="user-read-currently-playing", open_browser=False # 禁止启动浏览器 ) # 读取缓存token,过期自动刷新 token_info = auth_manager.get_cached_token() if not token_info or auth_manager.is_token_expired(token_info): token_info = auth_manager.refresh_access_token(token_info["refresh_token"]) access_token = token_info["access_token"] global LAST_REFRESH LAST_REFRESH = datetime.datetime.now() print("Access token refreshed", flush=True) # 强制刷新输出缓冲 return access_token
- 缓存文件挂载到容器
通过Docker卷挂载本地.cache文件到容器,避免敏感信息打包进镜像:
# docker-compose.yml 新增volumes配置 version: '3.1' services: test: image: test build: context: . dockerfile: Dockerfile container_name: test volumes: - ./.cache:/app/.cache # 挂载本地缓存文件
二、修复控制台无输出问题
通过两种方式强制Python输出实时显示:
- 脚本中添加flush=True
所有print语句末尾加上flush=True,确保输出不缓冲:
print('Error: Unable to get current track', flush=True)
- Docker启动命令关闭Python缓冲
修改Dockerfile的启动命令,添加-u参数:
FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # -u 参数关闭Python输出缓冲 CMD ["python", "-u", "main.py"]
修改后的完整脚本
import datetime import json import os import time import requests import spotipy from spotipy.oauth2 import SpotifyOAuth LAST_REFRESH = None def refresh_access_token(): auth_manager = SpotifyOAuth( client_id="<foo>", client_secret="<foo>", redirect_uri="http://localhost:8888", scope="user-read-currently-playing", open_browser=False ) token_info = auth_manager.get_cached_token() if not token_info or auth_manager.is_token_expired(token_info): token_info = auth_manager.refresh_access_token(token_info["refresh_token"]) access_token = token_info["access_token"] global LAST_REFRESH LAST_REFRESH = datetime.datetime.now() print("Access token refreshed", flush=True) return access_token def get_current_track(access_token): try: response = requests.get( 'https://api.spotify.com/v1/me/player/currently-playing', headers={ "Authorization": f"Bearer {access_token}" } ) json_resp = response.json() track_id = json_resp['item']['id'] track_name = json_resp['item']['name'] artists = [artist for artist in json_resp['item']['artists']] link = json_resp['item']['external_urls']['spotify'] artist_names = ', '.join([artist['name'] for artist in artists]) current_track_info = { "id": track_id, "track_name": track_name, "artists": artist_names, "link": link } return current_track_info except Exception as e: print(f'Error: Unable to get current track - {str(e)}', flush=True) time.sleep(10) return None def send_to_server(spotifyID): print(spotifyID, flush=True) #... def main(): current_track_id = None access_token = refresh_access_token() while True: try: if (datetime.datetime.now() - LAST_REFRESH).seconds > 3500: access_token = refresh_access_token() current_track_info = get_current_track(access_token) if current_track_info and current_track_info['id'] != current_track_id: current_track_id = current_track_info['id'] send_to_server(current_track_id) time.sleep(1) except Exception as e: print(f'Error: Main loop exception - {str(e)}', flush=True) time.sleep(10) if __name__ == '__main__': main()
内容的提问来源于stack exchange,提问作者Goethe
相关产品推荐
相关产品推荐

