#version 1.1
import os
import sys
import time
import re
import json
import requests
from PyQt5.QtGui import QFont, QIcon
from bs4 import BeautifulSoup
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QListWidget, QListWidgetItem,
QProgressBar, QFileDialog, QMessageBox,
QStatusBar, QCheckBox)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from requests import Session
from requests.adapters import HTTPAdapter
from urllib3 import Retry
class MusicDownloaderThread(QThread):
"""多線程下載類"""
progress_signal = pyqtSignal(int, int, int) # 當前下載序號, 總數量, 進度百分比
finished_signal = pyqtSignal(int, str, bool) # 索引, 文件名, 是否成功
error_signal = pyqtSignal(str)
def __init__(self, music_list, download_path):
super().__init__()
self.music_list = music_list
self.download_path = download_path
self.session = Session()
self.running = True
self.headers = {
'Referer': '',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
}
def run(self):
"""執行下載任務"""
total = len(self.music_list)
for idx, (name, url) in enumerate(self.music_list):
if not self.running:
break
self.progress_signal.emit(idx + 1, total, 0)
try:
# 獲取音樂ID
ask_id = re.compile('')
music_id = re.findall(ask_id, url)[0]
# 獲取實際下載鏈接
music_data = self.session.post(
'',
headers=self.headers,
data={'id': music_id, 'type': 'music'},
timeout=30
).json()
# 下載音樂
music_url = music_data.get('url')
filename = re.sub(r'[\\/*?:"<>|]', "", name) + '.mp3'
filepath = os.path.join(self.download_path, filename)
response = self.session.get(
music_url,
headers={'User-Agent': self.headers['User-Agent']},
stream=True,
timeout=60
)
# 帶進度下載
total_size = int(response.headers.get('content-length', 0))
downloaded = 0
with open(filepath, 'wb') as f:
for chunk in response.iter_content(chunk_size=1024):
if not self.running:
break
if chunk:
f.write(chunk)
downloaded += len(chunk)
progress = int((downloaded / total_size) * 100) if total_size > 0 else 0
self.progress_signal.emit(idx + 1, total, progress)
if self.running:
self.finished_signal.emit(idx, filename, True)
except Exception as e:
if self.running:
self.finished_signal.emit(idx, name, False)
self.error_signal.emit(f"下載失敗: {name} - {str(e)}")
def stop(self):
"""停止下載"""
self.running = False
class MusicDownloaderApp(QMainWindow):
"""音樂下載器主界面"""
def __init__(self):
super().__init__()
# 設置默認下載路徑
self.default_path = "E:\\music"
self.setWindowIcon(QIcon("music.ico")) # 支持.ico、.png等格式[3,4,6](@ref)
# self.initUI()
# 嘗試從文件加載保存的路徑
self.download_path = self.load_saved_path()
os.makedirs(self.download_path, exist_ok=True)
self.init_ui()
self.setup_connections()
# 初始化會話
self.session = Session()
self.session_ask()
def load_saved_path(self):
"""從文件加載保存的下載路徑"""
try:
if os.path.exists("download_path.txt"):
with open("download_path.txt", "r") as f:
saved_path = f.read().strip()
if saved_path and os.path.isdir(saved_path):
return saved_path
except:
pass
return self.default_path
def save_path_to_file(self, path):
"""保存路徑到文件"""
try:
with open("download_path.txt", "w") as f:
f.write(path)
except Exception as e:
self.show_error(f"保存路徑失敗: {str(e)}")
def init_ui(self):
"""初始化用戶界面"""
self.setWindowTitle("音樂下載器")
self.setGeometry(300, 200, 800, 500)
# 主布局
main_widget = QWidget()
self.setCentralWidget(main_widget)
main_layout = QVBoxLayout(main_widget)
# 頂部搜索區域
search_layout = QHBoxLayout()
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("請輸入歌曲名稱或歌手...")
search_layout.addWidget(self.search_input)
self.search_btn = QPushButton("搜索")
search_layout.addWidget(self.search_btn)
# 下載路徑選擇
self.path_btn = QPushButton("選擇下載路徑")
search_layout.addWidget(self.path_btn)
# 顯示當前路徑
self.path_label = QLabel(f"當前下載路徑: {self.download_path}")
search_layout.addWidget(self.path_label)
main_layout.addLayout(search_layout)
# 搜索結果區域
result_group = QVBoxLayout()
result_group.addWidget(QLabel("搜索結果"))
self.result_list = QListWidget()
self.result_list.setSelectionMode(QListWidget.ExtendedSelection) # 多選模式
result_group.addWidget(self.result_list)
# 下載控制
download_control_layout = QHBoxLayout()
self.download_btn = QPushButton("下載選中")
self.select_all_btn = QPushButton("全選")
download_control_layout.addWidget(self.select_all_btn)
download_control_layout.addWidget(self.download_btn)
result_group.addLayout(download_control_layout)
# 批量下載選項
self.batch_download_check = QCheckBox("批量下載模式")
result_group.addWidget(self.batch_download_check)
main_layout.addLayout(result_group, 1)
# 進度條
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
main_layout.addWidget(self.progress_bar)
# 狀態欄 - 版權聲明
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
copyright_label = QLabel("該軟件僅供學習和娛樂,如有侵權請聯系刪除")
copyright_label.setFont(QFont("Arial", 8))
self.status_bar.addPermanentWidget(copyright_label)
def setup_connections(self):
"""設置信號連接"""
self.search_btn.clicked.connect(self.search_music)
self.download_btn.clicked.connect(self.download_selected)
self.select_all_btn.clicked.connect(self.select_all_results)
self.path_btn.clicked.connect(self.select_download_path)
def session_ask(self):
"""初始化會話"""
try:
url = ''
self.session.get(url=url, headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
}, timeout=10)
except Exception as e:
self.show_error(f"初始化失敗: {str(e)}")
def search_music(self):
"""搜索音樂"""
keyword = self.search_input.text().strip()
if not keyword:
self.show_warning("請輸入搜索關鍵詞")
return
self.result_list.clear()
self.search_btn.setEnabled(False)
self.search_btn.setText("搜索中...")
try:
# 配置請求重試策略
retry_strategy = Retry(
total=3, # 最大重試次數
backoff_factor=1, # 指數退避因子
status_forcelist=[500, 502, 503, 504], # 觸發重試的狀態碼
allowed_methods=["GET"] # 只對GET請求重試
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
play_list = [] # 存儲所有<li>元素
for page in range(1, 10):
query_url = f'。。。。/{keyword}/{page}.html'
#print(f"正在請求: {query_url}")
for attempt in range(3): # 每頁最多重試3次
try:
response = self.session.get(
query_url,
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
},
timeout=15 # 更合理的超時時間
)
response.raise_for_status() # 檢查HTTP狀態碼[5,9](@ref)
soup = BeautifulSoup(response.text, 'html.parser')
container = soup.find('div', class_='play_list')
# 檢查容器是否存在[1,5](@ref)
if container:
query_list = container.find_all('li')
#print(f"第{page}頁解析到{len(query_list)}個元素")
play_list.extend(query_list) # 扁平化存儲元素
break # 成功則跳出重試循環
else:
print(f"警告:第{page}頁未找到play_list容器")
except requests.exceptions.RequestException as e:
print(f"請求異常(嘗試{attempt + 1}/3): {e}")
time.sleep(2 ** attempt) # 指數退避等待[9](@ref)
except Exception as e:
print(f"解析異常: {e}")
break
else: # 重試全部失敗
pass
status_bar = self.statusBar()
# 檢查是否已存在左下角標簽
if hasattr(self, 'left_label') and self.left_label is not None:
# 從狀態欄移除舊標簽
status_bar.removeWidget(self.left_label) # 關鍵操作[7](@ref)
# 安全銷毀舊標簽對象
self.left_label.deleteLater()
self.left_label = None
# 創建新標簽(帶紅色字體)
self.left_label = QLabel(f"總共獲取到{len(play_list)}個結果")
self.left_label.setStyleSheet("color: red; font: 8pt 'Arial';") # 設置紅色字體
# 添加到左下角(左側區域)
status_bar.addWidget(self.left_label) # 默認左下角[1,7](@ref)
if play_list:
for idx, li in enumerate(play_list, 1):
try:
link = li.find('a')
if link: # 檢查<a>標簽是否存在[1](@ref)
name = link.text.strip()
href = '' + link['href']
list_item = QListWidgetItem(f"{idx}. {name}")
list_item.setData(Qt.UserRole, (name, href))
self.result_list.addItem(list_item)
except TypeError: # 處理NoneType異常[1](@ref)
print(f"無效的<li>元素: {li}")
else:
self.show_info("未找到相關結果")
except Exception as e:
self.show_error(f"搜索失敗: {str(e)}")
import traceback
traceback.print_exc() # 打印完整堆棧[2](@ref)
finally:
self.search_btn.setEnabled(True)
self.search_btn.setText("搜索")
def download_selected(self):
"""下載選中的音樂"""
selected_items = self.result_list.selectedItems()
if not selected_items:
self.show_warning("請先選擇要下載的歌曲")
return
# 準備下載列表
download_list = []
for item in selected_items:
name, url = item.data(Qt.UserRole)
download_list.append((name, url))
# 批量下載模式
if self.batch_download_check.isChecked():
self.start_download_thread(download_list)
else:
# 單曲下載模式
for name, url in download_list:
self.start_download_thread([(name, url)])
def start_download_thread(self, download_list):
"""啟動下載線程"""
if not download_list:
return
# 創建并啟動下載線程
self.download_thread = MusicDownloaderThread(download_list, self.download_path)
self.download_thread.progress_signal.connect(self.update_download_progress)
self.download_thread.finished_signal.connect(self.download_finished)
self.download_thread.error_signal.connect(self.show_error)
self.download_thread.start()
# 顯示進度條
self.progress_bar.setVisible(True)
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
self.download_btn.setEnabled(False)
def update_download_progress(self, current, total, progress):
"""更新下載進度"""
self.progress_bar.setFormat(f"下載中: {current}/{total} ({progress}%)")
self.progress_bar.setValue(progress)
def download_finished(self, index, filename, success):
"""下載完成處理"""
if success:
self.show_info(f"下載完成: {filename}")
# 檢查是否全部完成
if index == len(self.download_thread.music_list) - 1:
self.progress_bar.setVisible(False)
self.download_btn.setEnabled(True)
self.show_info("所有下載任務已完成!")
def select_download_path(self):
"""選擇下載路徑"""
path = QFileDialog.getExistingDirectory(
self,
"選擇下載目錄",
self.download_path,
QFileDialog.ShowDirsOnly
)
if path:
self.download_path = path
self.path_label.setText(f"當前下載路徑: {path}")
self.save_path_to_file(path)
self.show_info(f"下載路徑已設置為: {path}")
def select_all_results(self):
"""全選搜索結果"""
self.result_list.selectAll()
def show_error(self, message):
"""顯示錯誤信息"""
QMessageBox.critical(self, "錯誤", message)
def show_warning(self, message):
"""顯示警告信息"""
QMessageBox.warning(self, "警告", message)
def show_info(self, message):
"""顯示信息"""
QMessageBox.information(self, "提示", message)
def closeEvent(self, event):
"""關閉窗口時的處理"""
if hasattr(self, 'download_thread') and self.download_thread.isRunning():
self.download_thread.stop()
self.download_thread.wait()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
downloader = MusicDownloaderApp()
downloader.show()
sys.exit(app.exec_())
通過網盤分享的文件:音樂下載器 1.0.1.rar
鏈接: https://pan.baidu.com/s/1ksPvVFryM3uZv6XiwC551g?pwd=hrrp 提取碼: hrrp
可批量下載,批量下載前需要勾選批量下載框,可自定義保存路徑
![圖片[1]-2025年最新音樂下載器推薦:無廣告-極速下載_一鍵收藏你的最愛](http://www.oilmaxhydraulic.com.cn/wp-content/uploads/2025/08/d2b5ca33bd20250822085949.png)
![圖片[2]-2025年最新音樂下載器推薦:無廣告-極速下載_一鍵收藏你的最愛](http://www.oilmaxhydraulic.com.cn/wp-content/uploads/2025/08/d2b5ca33bd20250822090005.png)
? 版權聲明
THE END