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

如何在Docker环境中无浏览器、不使用Flask运行Spotipy脚本

解决Docker中Spotipy无头认证与无输出问题

问题核心

  1. 无头认证失败:Spotipy默认OAuth流程依赖浏览器交互,Docker无头环境无法触发该流程,导致脚本卡在认证环节。
  2. 无控制台输出:Python默认缓冲标准输出,加上脚本可能卡在认证步骤,导致容器启动后无任何反馈。

解决方案

一、适配Spotipy无头环境认证

采用离线授权模式,提前在本地完成一次认证获取refresh token,后续通过该token自动刷新access token,无需浏览器交互:

  1. 本地预生成授权缓存
    在本地(非Docker环境)运行原脚本,完成浏览器认证流程,生成.cache文件(包含refresh token等敏感信息)。

  2. 修改认证逻辑
    调整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
  1. 缓存文件挂载到容器
    通过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输出实时显示:

  1. 脚本中添加flush=True
    所有print语句末尾加上flush=True,确保输出不缓冲:
print('Error: Unable to get current track', flush=True)
  1. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 10:46:05