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

VK音乐下载器Python代码运行失败及vk_api导入异常求助

VK音乐下载器运行问题排查

我是纯Python新手,之前完全没接触过编程,最近开始学习,正在做一个VK平台音乐收听下载的应用,用vk_api库实现,代码是ChatGPT生成的。我已经修正了少量语法错误,也通过终端装了vk_api和PyQt5,但代码不管在终端还是VS Code里都跑不起来,VS Code还提示“Import 'vk_api' could not be resolved”。我试过重装Python、pip、PyQt5这些库,问题还是没解决,附上代码求帮忙排查更多错误。

import sys
import vk_api
import requests
import PyQt5
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QInputDialog, QMessageBox
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtCore import Qt, QThread, pyqtSignal

# Installing the application icon
app_icon = QIcon(r'C:\Program Files\vkloader\Icon.png')
# Stream class for loading audio recordings
class DownloadMusicThread(QThread):
    downloadProgress = pyqtSignal(int)

    def __init__(self, parent=None):
        super(DownloadMusicThread, self).__init__(parent)
        self.credentials = None
        self.playlist_id = None
        self.audio_ids = []
        self.download_dir = None

    def set_credentials(self, login, password):
        self.credentials = (login, password)

    def set_playlist_id(self, playlist_id):
        self.playlist_id = playlist_id

    def set_audio_ids(self, audio_ids):
        self.audio_ids = audio_ids

    def set_download_dir(self, download_dir):
        self.download_dir = download_dir

    def run(self):
        vk_session = vk_api.VkApi(*self.credentials)
        vk_session.auth()
        vk = vk_session.get_api()

        if self.playlist_id is not None:
            audios = vk.audio.get(owner_id=self.credentials[0], playlist_id=self.playlist_id)
            audio_ids = [audio['id'] for audio in audios['items']]
            self.audio_ids = audio_ids

        num_audios = len(self.audio_ids)
        for i, audio_id in enumerate(self.audio_ids):
            try:
                audio = vk.audio.getById(audio_id=audio_id)[0]
                audio_url = audio['url']
                artist = audio['artist'].replace('/', '_')
                title = audio['title'].replace('/', '_')
                filename = "{} - {}.mp3".format(artist, title)
                filepath = os.path.join(self.download_dir, filename)

                response = requests.get(audio_url, stream=True)
                with open(filepath, "wb") as audio_file:
                    for chunk in response.iter_content(chunk_size=1024):
                        audio_file.write(chunk)

                progress = int((i + 1) / num_audios * 100)
                self.downloadProgress.emit(progress)
            except Exception as e:
              print("Error downloading audio %s: %s" % (audio_id, e))

# Main window class
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('VK Music Downloader')
        self.setWindowIcon(app_icon)
        self.setFixedSize(400, 300)

        # Окно ввода логина и пароля
        login_label = QAction('Логин:', self)
        font = QFont()
        font.setPointSize(10)
        login_label.setFont(font)
        login_label.setDisabled(True)
        self.addToolBarBreak()
        self.toolbar = self.addToolBar('Вход')
        self.toolbar.addAction(login_label)
        self.login_edit = self.toolbar.addLineEdit()
        password_label = QAction('Пароль:', self)
        password_label.setFont(font)
        password_label.setDisabled(True)
        self.toolbar.addAction(password_label)
        self.password_edit = self.toolbar.addLineEdit()
        self.password_edit.setEchoMode(QLineEdit.Password)
        self.toolbar.addAction('Войти', self.login_check)

        # Window to select a playlist or specific audio recordings
        download_label = QAction('Из плейлиста:', self)
        download_label.setFont(font)
        download_label.setDisabled(True)
        self.addToolBarBreak()
        self.toolbar = self.addToolBar('Загрузка')
        self.toolbar.addAction(download_label)
        self.playlist_radio = self.toolbar.addRadioButton('Плейлист')
        self.playlist_radio.toggled.connect(self.toggle_playlist_field)
        self.playlist_field = self.toolbar.addComboBox()
        self.playlist_field.setDisabled(True)
        self.toolbar.addAction('Обзор...', self.open_file_dialog)
        self.audios_radio = self.toolbar.addRadioButton('Аудиозаписи')
        self.audios_radio.toggled.connect(self.toggle_audios_field)
        self.audios_field = self.toolbar.addLineEdit()
        self.audios_field.setDisabled(True)

# Window for selecting the download directory
        folder_label = QAction('Сохранять в:', self)
        folder_label.setFont(font)
        folder_label.setDisabled(True)
        self.addToolBarBreak()
        self.toolbar = self.addToolBar('Сохранение')
        self.toolbar.addAction(folder_label)
        self.folder_edit = self.toolbar.addLineEdit()
        self.toolbar.addAction('Обзор...', self.open_dir_dialog)

# Start download button
        self.addToolBarBreak()
        self.toolbar = self.addToolBar('Начало загрузки')
        self.download_button = self.toolbar.addAction('Загрузить')
        self.download_button.setDisabled(True)
        self.download_button.triggered.connect(self.start_download)

        # Associating the Music Download Stream with the Main Window Signals
        self.download_thread = DownloadMusicThread()
        self.download_thread.downloadProgress.connect(self.update_progress)

# Checking the correctness of the login and password
    def login_check(self):
        login = self.login_edit.text().strip()
        password = self.password_edit.text().strip()
        if not login or not password:
            QMessageBox.warning(self, 'Ошибка', 'Введите логин и пароль')
            return
        self.download_button.setEnabled(False)
        self.playlist_field.clear()
        try:
            vk_session = vk_api.VkApi(login, password)
            vk_session.auth()
            vk = vk_session.get_api()
            playlists = vk.audio.getPlaylists()
            for playlist in playlists['items']:
                self.playlist_field.addItem(playlist['title'], playlist['id'])
        except vk_api.AuthError:
            QMessageBox.warning(self, 'Ошибка', 'Не удалось авторизоваться')
            return
        self.playlist_field.setEnabled(True)
        self.download_button.setEnabled(True)

# Switch between playlist selection and specific audio recordings
    def toggle_playlist_field(self, checked):
        self.playlist_field.setEnabled(checked)
        self.audios_field.setDisabled(checked)

    def toggle_audios_field(self, checked):
        self.audios_field.setEnabled(checked)
        self.playlist_field.setDisabled(checked)

    # Opening the dialog for selecting a playlist or files with audio recordings to download
    def open_file_dialog(self):
        if self.playlist_radio.isChecked():
            self.playlist_field.showPopup()
        else:
            options = QFileDialog.Options()
            options |= QFileDialog.DontUseNativeDialog
            filenames, _ = QFileDialog.getOpenFileNames(self, "Выбрать аудиозаписи для загрузки", "",
                                                        "Аудио файлы (*.mp3);;Все файлы (*)", options=options)
            if filenames:
                self.audios_field.setText("\n".join(filenames))

    # Opening the dialog for selecting a directory to save files
    def open_dir_dialog(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        foldername = QFileDialog.getExistingDirectory(self, "Выбрать директорию для сохранения", options=options)
        if foldername:
            self.folder_edit.setText(foldername)

# Start downloading music
    def start_download(self):
        login = self.login_edit.text().strip()
        password = self.password_edit.text().strip()
        if not login or not password:
            QMessageBox.warning(self, 'Ошибка', 'Введите логин и пароль')
            return

        if self.download_thread.isRunning():
            QMessageBox.warning(self, 'Ошибка', 'Загрузка уже запущена')
            return

        self.download_thread.set_credentials(login, password)

        if self.playlist_radio.isChecked():
            playlist_index = self.playlist_field.currentIndex()
            if playlist_index >= 0:
                playlist_id = self.playlist_field.itemData(playlist_index)
                self.download_thread.set_playlist_id(playlist_id)
        else:
            audio_ids = [audio_id.strip() for audio_id in self.audios_field.toPlainText().split('\n')]
            self.download_thread.set_audio_ids(audio_ids)

        download_dir = self.folder_edit.text().strip()
        if not download_dir:
            QMessageBox.warning(self, 'Ошибка', 'Выберите директорию для сохранения')
            return
        self.download_thread.set_download_dir(download_dir)

        self.download_thread.start()

# Updating the loading indicator
    def update_progress(self, progress):
       self.statusBar().showMessage("Загружено %s%%" % progress)
       if progress >= 100:
            QMessageBox.information(self, 'Загрузка завершена', 'Файлы успешно загружены')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setApplicationDisplayName('VKloader')
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

问题排查与修复方案

一、解决VS Code的导入解析错误

  • 确认解释器一致性:点击VS Code左下角的Python版本号,选择你安装了vk_api和PyQt5的Python环境(比如系统默认环境或虚拟环境)
  • 虚拟环境验证:如果用了虚拟环境,先激活再重新安装依赖:
    # Windows
    venv\Scripts\activate
    # macOS/Linux
    source venv/bin/activate
    pip install vk-api pyqt5 requests
    
  • 重启VS Code:等待语言服务重新加载后再尝试运行代码

二、代码中的语法与逻辑错误修复

  1. 缺失模块导入

    • 代码使用了os.path.join但未导入os,需在开头添加:
      import os
      
    • 使用了QLineEdit.Password但未导入该类,修改导入语句:
      from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QInputDialog, QMessageBox, QLineEdit
      
  2. 音频ID格式错误
    vk.audio.getById要求音频ID格式为owner_id_audio_id,原代码仅传递audio['id']会导致接口调用失败,修改DownloadMusicThread的run方法:

    # 原代码
    audio_ids = [audio['id'] for audio in audios['items']]
    # 修改为
    audio_ids = [f"{audio['owner_id']}_{audio['id']}" for audio in audios['items']]
    
  3. 图标路径异常处理
    硬编码的图标路径可能不存在,添加异常处理避免崩溃:

    try:
        app_icon = QIcon(r'C:\Program Files\vkloader\Icon.png')
    except:
        app_icon = QIcon()  # 用系统默认图标替代
    
  4. 线程重复运行问题
    原线程只能运行一次,添加重置逻辑允许重复启动:
    在start_download方法开头添加:

    if not self.download_thread.isFinished():
        self.download_thread.quit()
        self.download_thread.wait()
    self.download_thread = DownloadMusicThread()
    self.download_thread.downloadProgress.connect(self.update_progress)
    
  5. 登录时owner_id错误
    原代码用登录账号(可能是邮箱/手机号)作为owner_id,需从会话中获取正确的用户ID:
    在login_check方法中添加:

    user_id = vk_session.user_id
    

    后续调用vk.audio.get时使用该user_id而非登录账号

三、修复后的完整代码

import sys
import os
import vk_api
import requests
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QInputDialog, QMessageBox, QLineEdit
from PyQt5.QtGui import QIcon, QFont
from PyQt5.QtCore import Qt, QThread, pyqtSignal

# 加载应用图标,处理路径不存在情况
try:
    app_icon = QIcon(r'C:\Program Files\vkloader\Icon.png')
except:
    app_icon = QIcon()

# 音乐下载线程类
class DownloadMusicThread(QThread):
    downloadProgress = pyqtSignal(int)

    def __init__(self, parent=None):
        super(DownloadMusicThread, self).__init__(parent)
        self.credentials = None
        self.playlist_id = None
        self.audio_ids = []
        self.download_dir = None
        self.user_id = None

    def set_credentials(self, login, password):
        self.credentials = (login, password)

    def set_playlist_id(self, playlist_id):
        self.playlist_id = playlist_id

    def set_audio_ids(self, audio_ids):
        self.audio_ids = audio_ids

    def set_download_dir(self, download_dir):
        self.download_dir = download_dir

    def run(self):
        try:
            vk_session = vk_api.VkApi(*self.credentials)
            vk_session.auth()
            vk = vk_session.get_api()
            self.user_id = vk_session.user_id

            if self.playlist_id is not None:
                audios = vk.audio.get(owner_id=self.user_id, playlist_id=self.playlist_id)
                # 保存正确格式的音频ID:owner_id_audio_id
                audio_ids = [f"{audio['owner_id']}_{audio['id']}" for audio in audios['items']]
                self.audio_ids = audio_ids

            num_audios = len(self.audio_ids)
            if num_audios == 0:
                self.downloadProgress.emit(100)
                return

            for i, audio_id in enumerate(self.audio_ids):
                try:
                    audio = vk.audio.getById(audio_id=audio_id)[0]
                    audio_url = audio['url']
                    artist = audio['artist'].replace('/', '_')
                    title = audio['title'].replace('/', '_')
                    filename = f"{artist} - {title}.mp3"
                    filepath = os.path.join(self.download_dir, filename)

                    # 处理文件重名
                    counter = 1
                    while os.path.exists(filepath):
                        filename = f"{artist} - {title}_{counter}.mp3"
                        filepath = os.path.join(self.download_dir, filename)
                        counter += 1

                    response = requests.get(audio_url, stream=True)
                    response.raise_for_status()  # 检查请求是否成功
                    with open(filepath, "wb") as audio_file:
                        for chunk in response.iter_content(chunk_size=1024*1024):  # 用更大的块提高下载速度
                            if chunk:
                                audio_file.write(chunk)

                    progress = int((i + 1) / num_audios * 100)
                    self.downloadProgress.emit(progress)
                except Exception as e:
                    print(f"下载音频 {audio_id} 出错: {str(e)}")
        except Exception as e:
            print(f"会话初始化出错: {str(e)}")

# 主窗口类
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('VK Music Downloader')
        self.setWindowIcon(app_icon)
        self.setFixedSize(400, 300)

        # 登录栏
        login_label = QAction('Логин:', self)
        font = QFont()
        font.setPointSize(10)
        login_label.setFont(font)
        login_label.setDisabled(True)
        self.addToolBarBreak()
        self.toolbar_login = self.addToolBar('Вход')
        self.toolbar_login.addAction(login_label)
        self.login_edit = self.toolbar_login.addLineEdit()
        password_label = QAction('Пароль:', self)
        password_label.setFont(font)
        password_label.setDisabled(True)
        self.toolbar_login.addAction(password_label)
        self.password_edit = self.toolbar_login.addLineEdit()
        self.password_edit.setEchoMode(QLineEdit.Password)
        self.toolbar_login.addAction('Войти', self.login_check)

        # 下载选项栏
        download_label = QAction('Из плейлиста:',
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 04:50:43