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

Python代码已读取文件却仍提示文件未找到的问题排查及代码优化建议

Python代码已读取文件却仍提示文件未找到的问题排查及代码优化建议

首先,我来帮你拆解这个看似矛盾的问题:你明明看到代码已经开始处理文件里的专辑链接了,却最后弹出“urls.txt未找到”的错误,这确实很误导人。我们先理清问题根源,再给你针对性的优化方案。

问题环境

  • 运行平台:ThinkPAD laptop / Windows 11 / Microsoft Visual Studio Code
  • Python版本:Python 3.13.1
  • 需求:通过存储在urls.txt里的Spotify专辑链接,批量下载专辑内所有曲目

你的代码与异常输出

核心代码

import subprocess
import os
import time
import re
from spotipy import Spotify
from spotipy.oauth2 import SpotifyClientCredentials

def get_album_tracks(album_url, spotify_client):
    """Retrieve all track URLs from an album URL."""
    try:
        # Extract album ID from the URL
        match = re.match(r"https?://open\.spotify\.com/album/([a-zA-Z0-9]+)", album_url)
        if not match:
            print(f"Invalid album URL: {album_url}")
            return []

        album_id = match.group(1)

        # Fetch album tracks
        tracks = spotify_client.album_tracks(album_id)
        track_links = [track['external_urls']['spotify'] for track in tracks['items']]

        return track_links
    except Exception as e:
        print(f"Error retrieving tracks for album {album_url}: {e}")
        return []

def download_songs_from_file(file_path, download_directory, client_id, client_secret, max_retries=3):
    try:
        # Ensure the download directory exists
        os.makedirs(download_directory, exist_ok=True)

        # Authenticate with Spotify API
        spotify_client = Spotify(client_credentials_manager=SpotifyClientCredentials(client_id, client_secret))

        # Read album links from the file
        with open(file_path, 'r') as file:
            album_links = [link.strip() for link in file if link.strip()]  # Remove empty lines and whitespace

        total_albums = len(album_links)
        total_tracks = 0
        successful_downloads = 0

        print(f"Total albums to process: {total_albums}")

        for album_index, album_link in enumerate(album_links, start=1):
            print(f"\nProcessing album ({album_index}/{total_albums}): {album_link}")

            # Get all track links from the album
            track_links = get_album_tracks(album_link, spotify_client)
            track_count = len(track_links)
            total_tracks += track_count

            print(f"Found {track_count} tracks in album: {album_link}")

            for track_index, track_link in enumerate(track_links, start=1):
                print(f"Downloading track ({track_index}/{track_count}): {track_link}")
                retries = 0
                while retries < max_retries:
                    try:
                        # Run the spotdl command
                        subprocess.run(
                            ["spotdl", "download", track_link, "--path", download_directory],
                            check=True
                        )
                        print(f"Downloaded successfully: {track_link}")
                        successful_downloads += 1
                        break
                    except subprocess.CalledProcessError as e:
                        retries += 1
                        print(f"Error downloading: {track_link} (Attempt {retries}/{max_retries}) - {e}")
                        if retries >= max_retries:
                            print(f"Skipping after {max_retries} attempts.")
                        else:
                            time.sleep(2)  # Short delay before retrying

        # Count the actual files in the download directory
        downloaded_files = len([f for f in os.listdir(download_directory) if os.path.isfile(os.path.join(download_directory, f))])

        print(f"\nDownload summary:")
        print(f"Total albums in list: {total_albums}")
        print(f"Total tracks found: {total_tracks}")
        print(f"Successfully downloaded: {successful_downloads}")
        print(f"Failed downloads: {total_tracks - successful_downloads}")
        print(f"Total files in '{download_directory}': {downloaded_files}")

    except FileNotFoundError:
        print(f"The file {file_path} was not found.")
    except Exception as e:
        print(f"An error occurred: {e}")

# Example usage
file_path = "C:\\Users\\myname\\Music\\BEYOND\\urls.txt"  # File containing album links
download_directory = "C:\\Users\\myname\\Music\\BEYOND"  # Directory for downloads
client_id = "my_client_id"  # Spotify API Client ID
client_secret = "my_client_sectet"  # Spotify API Client Secret

download_songs_from_file(file_path, download_directory, client_id, client_secret)

异常输出

Processing album (1/50): https://open.spotify.com/album/6oGvml3VgdRhNCTRX32Tbs
Found 12 tracks in album: https://open.spotify.com/album/6oGvml3VgdRhNCTRX32Tbs
Downloading track (1/12): https://open.spotify.com/track/466cmnbIuOeQ3aKpijfvmf
The file C:\Users\myname\Music\BEYOND\urls.txt was not found.

问题根源分析

你说得完全没错,代码确实已经读取了urls.txt的内容(否则不会显示处理专辑的日志)。问题出在错误捕获的范围太宽泛:

整个download_songs_from_file函数都被包裹在一个大的try块里,而except FileNotFoundError直接粗暴地输出“urls.txt未找到”。但实际上,在后续的执行流程中(比如:

  • spotdl命令执行时找不到临时缓存文件
  • 读取下载目录时出现权限或路径问题
  • 甚至os.listdir访问下载目录失败)
    都可能抛出FileNotFoundError,这时候就会被这个except块捕获,错误地把锅甩给urls.txt。

另外,我还注意到你代码里的一个拼写错误:client_secret被写成了my_client_sectet,这可能会导致Spotify API认证失败,也是需要修正的点。

解决方案与代码优化

1. 精准捕获文件读取错误

把读取urls.txt的代码单独放在小的try-except块里,只在这里处理文件未找到的情况,其他地方的FileNotFoundError单独处理,避免误导。

2. 优化错误提示

区分不同来源的FileNotFoundError,比如检查错误信息里的文件名,或者针对不同操作单独捕获异常。

3. 其他实用优化建议

  • 使用pathlib处理路径:Windows下的路径更安全,避免转义字符问题
  • spotdl直接支持专辑链接:不需要逐个解析曲目,效率更高,减少API调用和subprocess开销
  • 增加参数验证:提前检查client_id、client_secret、路径是否合法
  • 替换print为logging:方便后续排查问题,日志更规范
  • 避免硬编码:可以用命令行参数或配置文件管理路径和API密钥

优化后的代码示例

import subprocess
import time
import re
import logging
from pathlib import Path
from spotipy import Spotify
from spotipy.oauth2 import SpotifyClientCredentials

# 配置日志,比print更便于排查问题
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def get_album_tracks(album_url, spotify_client):
    """Retrieve all track URLs from an album URL."""
    try:
        match = re.match(r"https?://open\.spotify\.com/album/([a-zA-Z0-9]+)", album_url)
        if not match:
            logger.error(f"Invalid album URL: {album_url}")
            return []

        album_id = match.group(1)
        tracks = spotify_client.album_tracks(album_id)
        track_links = [track['external_urls']['spotify'] for track in tracks['items']]

        return track_links
    except Exception as e:
        logger.error(f"Error retrieving tracks for album {album_url}: {str(e)}")
        return []

def download_songs_from_file(file_path, download_directory, client_id, client_secret, max_retries=3):
    # 提前验证API密钥合法性
    if not client_id or not client_secret:
        logger.error("Client ID or Client Secret cannot be empty")
        return

    # 转换为Path对象,自动处理Windows/Linux路径差异
    file_path = Path(file_path)
    download_directory = Path(download_directory)

    # 确保下载目录存在
    try:
        download_directory.mkdir(parents=True, exist_ok=True)
    except Exception as e:
        logger.error(f"Failed to create download directory {download_directory}: {str(e)}")
        return

    # 单独处理专辑列表文件的读取错误
    try:
        with file_path.open('r', encoding='utf-8') as file:
            album_links = [link.strip() for link in file if link.strip()]
    except FileNotFoundError:
        logger.error(f"The album list file was not found: {file_path}")
        return
    except PermissionError:
        logger.error(f"No permission to read the file: {file_path}")
        return
    except Exception as e:
        logger.error(f"Failed to read album list file: {str(e)}")
        return

    total_albums = len(album_links)
    if total_albums == 0:
        logger.info("No album links found in the file")
        return

    logger.info(f"Total albums to process: {total_albums}")

    # 初始化Spotify客户端
    try:
        spotify_client = Spotify(client_credentials_manager=SpotifyClientCredentials(client_id, client_secret))
    except Exception as e:
        logger.error(f"Failed to authenticate with Spotify API: {str(e)}")
        return

    total_tracks = 0
    successful_downloads = 0

    for album_index, album_link in enumerate(album_links, start=1):
        logger.info(f"\nProcessing album ({album_index}/{total_albums}): {album_link}")

        track_links = get_album_tracks(album_link, spotify_client)
        track_count = len(track_links)
        total_tracks += track_count

        logger.info(f"Found {track_count} tracks in album: {album_link}")

        # 优化点:spotdl支持直接下载整个专辑,不需要逐个处理曲目,效率更高
        # 可以替换下面的循环为这一行:
        # subprocess.run(["spotdl", "download", album_link, "--path", str(download_directory)], check=True)

        for track_index, track_link in enumerate(track_links, start=1):
            logger.info(f"Downloading track ({track_index}/{track_count}): {track_link}")
            retries = 0
            success = False
            while retries < max_retries:
                try:
                    # 捕获spotdl的输出,避免冗余日志
                    subprocess.run(
                        ["spotdl", "download", track_link, "--path", str(download_directory)],
                        check=True,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True
                    )
                    logger.info(f"Downloaded successfully: {track_link}")
                    successful_downloads += 1
                    success = True
                    break
                except subprocess.CalledProcessError as e:
                    retries += 1
                    logger.error(f"Error downloading {track_link} (Attempt {retries}/{max_retries}): {e.stderr}")
                    if retries >= max_retries:
                        logger.warning(f"Skipping {track_link} after {max_retries} attempts.")
                    else:
                        time.sleep(2)
                except FileNotFoundError:
                    logger.error(f"spotdl command not found! Please ensure spotdl is installed and added to PATH.")
                    return
                except Exception as e:
                    logger.error(f"Unexpected error downloading {track_link}: {str(e)}")
                    break

    # 统计下载文件数量
    try:
        downloaded_files = len([f for f in download_directory.iterdir() if f.is_file()])
    except Exception as e:
        logger.error(f"Failed to count files in download directory: {str(e)}")
        downloaded_files = -1

    logger.info("\nDownload summary:")
    logger.info(f"Total albums in list: {total_albums}")
    logger.info(f"Total tracks found: {total_tracks}")
    logger.info(f"Successfully downloaded: {successful_downloads}")
    logger.info(f"Failed downloads: {total_tracks - successful_downloads}")
    if downloaded_files != -1:
        logger.info(f"Total files in '{download_directory}': {downloaded_files}")

# Example usage
if __name__ == "__main__":
    # 使用/作为路径分隔符,Path会自动转换为Windows格式
    file_path = "C:/Users/myname/Music/BEYOND/urls.txt"
    download_directory = "C:/Users/myname/Music/BEYOND"
    client_id = "my_client_id"
    client_secret = "my_client_secret"  # 修正了拼写错误

    download_songs_from_file(file_path, download_directory, client_id, client_secret)

额外注意点

  • 确保spotdl已经正确安装并添加到系统PATH中,否则会抛出“找不到命令”的错误
  • Spotify API有调用频率限制,批量下载时注意不要过于频繁,必要时可以增加延迟
  • 建议使用虚拟环境管理依赖,避免版本冲突

备注:内容来源于stack exchange,提问作者beesuns

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 03:20:35