网易云音乐歌单批量下载歌曲(python)

前言

使用metting批量下载网易云音乐歌曲
使用前确保已安装 requests、librosa 和 urllib 库

代码

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import os
import json
import requests
import librosa
from urllib.parse import quote
import time
import signal
import sys

def download_song(url, song_path, song_index, total_songs):
    try:
        response = requests.get(url, stream=True)
        response.raise_for_status()  # 检查请求是否成功
        total_size = int(response.headers.get('content-length', 0))
        downloaded_size = 0
        
        with open(song_path, "wb") as song_file:
            for data in response.iter_content(chunk_size=4096):
                downloaded_size += len(data)
                song_file.write(data)
                progress = downloaded_size / total_size * 100 if total_size > 0 else 0
                print(f"正在下载 [{song_index}/{total_songs}] {song_path},进度:{progress:.2f}%\r", end="")
        
        print(f"下载完成 [{song_index}/{total_songs}] {song_path}")
        return True
    except requests.exceptions.RequestException as e:
        print(f"下载 [{song_index}/{total_songs}] {song_path} 失败:{e}")
        return False

# def safe_filename(filename):
#     # 将特殊字符(/)改成(-)
#     return filename.replace("/", "-")

def safe_filename(filename):
    # 将无效字符替换为下划线
    invalid_chars = '\\/:*?"<>|'
    for char in invalid_chars:
        filename = filename.replace(char, '_')
    return filename

def delete_file(file_path):
    if os.path.exists(file_path):
        os.remove(file_path)
        print(f"删除文件:{file_path}")

def download_playlist(playlist_id, download_path):
    if not playlist_id.isdigit():
        print("歌单ID必须为数字")
        return

    # JSON链接
    api_urls = [
        "https://api.injahow.cn/meting/?type=playlist&id=",
        "https://api.injahow.cn/meting/?type=playlist&id=",
        "https://meting.qjqq.cn/?type=playlist&id="
    ]

    selected_api = None
    data = None

    for api_url in api_urls:
        try:
            response = requests.get(f"{api_url}{playlist_id}")
            response.raise_for_status()  # 检查请求是否成功
            data = response.json()
            selected_api = api_url
            if 'error' in data:
                error = data.get("error", "")
                print("出错了...")
                print(f"错误详细:{error}")
                return  # Exit the function if error is encountered in JSON response
            break
        except requests.exceptions.RequestException as e:
            print(f"API {api_url} 请求失败:{e}")
            continue

    if not data:
        print("所有API都无法获取数据")
        return

    # 创建下载路径
    os.makedirs(download_path, exist_ok=True)

    total_songs = len(data)
    print(f"歌单共有 {total_songs} 首歌曲")

    successful_downloads = 0
    failed_downloads = []

    def signal_handler(sig, frame):
        print("检测到Ctrl+C, exiting gracefully...")
        print(f"程序运行完成,共 {total_songs} 首歌曲,成功下载 {successful_downloads} 首歌曲")
        if failed_downloads:
            print(f"共有 {len(failed_downloads)} 首歌曲下载失败,id为:{','.join(failed_downloads)}")
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)

    for index, song in enumerate(data, start=1):
        # 歌曲信息
        name = song.get("name", "")
        artist = song.get("artist", "")
        url = song.get("url", "")
        lrc = song.get("lrc", "")
        # duration = song.get("duration", 0)  # 歌曲时长,单位:秒

        if not name or not artist or not url or not lrc:
            print(f"歌曲信息不完整:{song}")
            continue

        # 歌曲文件名和路径
        song_filename = f"{safe_filename(name)} - {safe_filename(artist)}.mp3"
        song_path = os.path.join(download_path, song_filename)

        # 下载歌曲
        retry_count = 0
        while retry_count < 5:
            if download_song(url, song_path, index, total_songs):
                successful_downloads += 1
                break
            else:
                retry_count += 1
                print(f"重试下载 [{index}/{total_songs}] {song_path},次数:{retry_count}")
                time.sleep(1)  # 等待1秒后重试
        
        # 下载歌词
        lrc_response = requests.get(lrc)
        lrc_filename = f"{safe_filename(name)} - {safe_filename(artist)}.lrc"
        lrc_path = os.path.join(download_path, lrc_filename)
        with open(lrc_path, "w", encoding="utf-8") as lrc_file:  # 修改这里,指定编码为utf-8
            lrc_file.write(lrc_response.content.decode('utf-8'))  # 将内容以utf-8解码后写入文件
            
        # 检查歌曲时长是否小于一分钟
#        if duration < 60:
#            print(f"歌曲时长小于一分钟,删除歌曲和歌词:{song_path}")
#            delete_file(song_path)
#            lrc_filename = f"{safe_filename(name)} - {safe_filename(artist)}.lrc"
#            lrc_path = os.path.join(download_path, lrc_filename)
#            delete_file(lrc_path)
#            failed_downloads.append(song.get('id', '')) # 添加到下载失败列表
#            successful_downloads -= 1 # 减去下载成功计数

                # 使用librosa获取音频持续时间
        try:
            audio_duration = librosa.get_duration(filename=song_path)
            if audio_duration < 60:
                print(f"歌曲时长小于一分钟,删除歌曲和歌词:{song_path}")
                delete_file(song_path)
                delete_file(lrc_path)
                failed_downloads.append(song.get('id', '')) # 添加到下载失败列表
                successful_downloads -= 1 # 减去下载成功计数
            else:
                print(f"歌曲时长为 {audio_duration} 秒")
        except Exception as e:
            print(f"无法获取歌曲时长:{e}")
            # failed_downloads.append(song.get('id', ''))

    print(f"程序运行完成,共 {total_songs} 首歌曲,成功下载 {successful_downloads} 首歌曲")
    if failed_downloads:
        # print(f"共有 {len(failed_downloads)} 首歌曲下载失败,id为:{','.join(failed_downloads)}")
        print(f"共有 {len(failed_downloads)} 首歌曲下载失败...")

if __name__ == "__main__":
    playlist_id = input("歌单ID:")
    download_path = input(r"下载路径(默认为 D:\down):") or r"D:\down"

    download_playlist(playlist_id, download_path)

Github:https://github.com/God-2077/python-code/tree/main/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E6%AD%8C%E5%8D%95%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BD%BD%E6%AD%8C%E6%9B%B2

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值