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

Spotify歌单提取YouTube链接脚本求助:仅获100首+403配额超限

解决Spotify歌单提取不全与YouTube API配额超限问题

一、Spotify歌单仅获取前100首的问题

你的主函数里调用了未定义的fetch_playlist_by_id函数,导致分页逻辑未生效,只能拿到默认的前100首。需要做以下修改:

  • 替换调用为你已实现的fetch_all_tracks函数
  • 单独通过Spotify API获取歌单名称

二、YouTube API配额超限403的问题

YouTube Data API免费版每天仅提供10000单位配额,单次搜索请求消耗100单位,所以100次搜索后就会触发403错误。优化方案:

  1. 让Google API客户端使用缓存,避免重复搜索同一曲目
  2. 添加异常捕获,处理配额超限情况
  3. 本地持久化缓存结果,重启脚本也能复用之前的搜索记录

修改后的完整代码

import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
from googleapiclient.discovery import build
import requests_cache
from requests_cache.contrib.cache_requests import CacheRequests
import time
import json
from pathlib import Path

# 启用requests缓存,同时让googleapiclient使用缓存
requests_cache.install_cache('youtube_cache', expire_after=86400)  # 缓存有效期设为1天
CacheRequests.install_cache('google_api_cache', expire_after=86400)

# Spotify API credentials (hidden)
SPOTIFY_CLIENT_ID = 'XXX'
SPOTIFY_CLIENT_SECRET = 'XXX'

# YouTube API key (hidden)
YOUTUBE_API_KEY = 'XXX'

# Spotify playlist ID (hidden)
SPOTIFY_PLAYLIST_ID = 'XXX'

# 本地缓存文件,存储已搜索过的曲目结果
CACHE_FILE = Path('youtube_search_cache.json')

# 加载本地缓存
def load_local_cache():
    if CACHE_FILE.exists():
        with open(CACHE_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {}

# 保存到本地缓存
def save_local_cache(cache_data):
    with open(CACHE_FILE, 'w', encoding='utf-8') as f:
        json.dump(cache_data, f, indent=2)

# Spotify authentication
sp = spotipy.Spotify(auth_manager=SpotifyClientCredentials(client_id=SPOTIFY_CLIENT_ID, client_secret=SPOTIFY_CLIENT_SECRET))

# YouTube API
youtube = build('youtube', 'v3', developerKey=YOUTUBE_API_KEY)

# Fetch all tracks from a playlist, handling pagination
def fetch_all_tracks(sp, playlist_id):
    all_tracks = []
    offset = 0
    limit = 100  # Spotify API单次最大返回100条
    while True:
        response = sp.playlist_tracks(playlist_id, offset=offset, limit=limit)
        tracks = response['items']
        if not tracks:
            break
        all_tracks.extend(tracks)
        offset += len(tracks)
        # 计算剩余条目,避免不必要的请求
        remaining = response['total'] - offset
        if remaining <= 0:
            break
        limit = min(100, remaining)
    return all_tracks

# 获取歌单名称
def get_playlist_name(sp, playlist_id):
    playlist = sp.playlist(playlist_id)
    return playlist['name']

# Extract track information
def extract_track_info(track):
    track_info = {
        'name': track['track']['name'],
        'artists': [artist['name'] for artist in track['track']['artists']]
    }
    return track_info

# Search for the respective track on YouTube using API key with rate limiting
def search_on_youtube(api_key, query, local_cache):
    # 先检查本地缓存
    cache_key = query.lower().strip()
    if cache_key in local_cache:
        return local_cache[cache_key]
    
    time.sleep(1)  # 限速,避免触发API频率限制
    try:
        request = youtube.search().list(q=query, part='snippet', type='video', maxResults=1)
        response = request.execute()
        if not response['items']:
            print(f"未找到匹配视频: {query}")
            return None
        video_id = response['items'][0]['id']['videoId']
        # 更新本地缓存
        local_cache[cache_key] = video_id
        save_local_cache(local_cache)
        return video_id
    except Exception as e:
        if 'quotaExceeded' in str(e):
            print("YouTube API配额已用尽,请明天再试或申请更高配额。")
            raise SystemExit(1)
        else:
            print(f"搜索出错: {str(e)}")
            return None

# Add video to YouTube Music (manual step)
def add_to_youtube_music(video_id):
    if video_id:
        print(f"添加到YouTube Music: https://www.youtube.com/watch?v={video_id}")
    else:
        print("无有效视频链接可添加")
    print("手动将该视频添加到你的YouTube Music库。")

# Transfer playlist to YouTube Music
def transfer_to_youtube_music(api_key, playlist_name, tracks):
    local_cache = load_local_cache()
    for idx, track in enumerate(tracks, 1):
        query = f"{track['name']} {', '.join(track['artists'])}"
        print(f"正在处理第 {idx}/{len(tracks)} 首: {query}")
        video_id = search_on_youtube(api_key, query, local_cache)
        add_to_youtube_music(video_id)

if __name__ == '__main__':
    # Fetch playlist from Spotify
    playlist_name = get_playlist_name(sp, SPOTIFY_PLAYLIST_ID)
    raw_tracks = fetch_all_tracks(sp, SPOTIFY_PLAYLIST_ID)
    # 提取曲目信息
    tracks = [extract_track_info(track) for track in raw_tracks]

    print(f"成功获取Spotify歌单 '{playlist_name}' 的 {len(tracks)} 首歌曲")

    # Transfer playlist to YouTube Music
    transfer_to_youtube_music(YOUTUBE_API_KEY, playlist_name, tracks)

    print(f"已完成歌单 '{playlist_name}' 的处理")

关键修改说明

  1. 修复Spotify分页问题:替换未定义的函数调用,新增get_playlist_name获取歌单名称,正确执行分页逻辑提取所有曲目
  2. 强化缓存机制:
    • 延长缓存有效期到1天
    • 添加本地JSON缓存文件,持久化存储搜索结果,重启脚本无需重复搜索
    • 让Google API客户端使用缓存,减少API调用次数
  3. 配额错误处理:捕获配额超限异常,提示用户并终止程序
  4. 进度提示:添加处理进度显示,方便跟踪执行状态
  5. 鲁棒性优化:处理搜索不到视频的情况,避免程序崩溃

内容的提问来源于stack exchange,提问作者Yusei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 08:43:20