Netease_url/music_api.py

214 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import urllib.parse
from random import randrange
import requests
from hashlib import md5
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def HexDigest(data):
return "".join([hex(d)[2:].zfill(2) for d in data])
def HashDigest(text):
HASH = md5(text.encode("utf-8"))
return HASH.digest()
def HashHexDigest(text):
return HexDigest(HashDigest(text))
def post(url, params, cookie):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 Chrome/91.0.4472.164 NeteaseMusicDesktop/2.10.2.200154',
'Referer': '',
}
cookies = {
"os": "pc",
"appver": "",
"osver": "",
"deviceId": "pyncm!"
}
cookies.update(cookie)
response = requests.post(url, headers=headers, cookies=cookies, data={"params": params})
return response.text
def url_v1(id, level, cookies):
url = "https://interface3.music.163.com/eapi/song/enhance/player/url/v1"
AES_KEY = b"e82ckenh8dichen8"
config = {
"os": "pc",
"appver": "",
"osver": "",
"deviceId": "pyncm!",
"requestId": str(randrange(20000000, 30000000))
}
payload = {
'ids': [id],
'level': level,
'encodeType': 'flac',
'header': json.dumps(config),
}
if level == 'sky':
payload['immerseType'] = 'c51'
url2 = urllib.parse.urlparse(url).path.replace("/eapi/", "/api/")
digest = HashHexDigest(f"nobody{url2}use{json.dumps(payload)}md5forencrypt")
params = f"{url2}-36cd479b6b5-{json.dumps(payload)}-36cd479b6b5-{digest}"
padder = padding.PKCS7(algorithms.AES(AES_KEY).block_size).padder()
padded_data = padder.update(params.encode()) + padder.finalize()
cipher = Cipher(algorithms.AES(AES_KEY), modes.ECB())
encryptor = cipher.encryptor()
enc = encryptor.update(padded_data) + encryptor.finalize()
params = HexDigest(enc)
response = post(url, params, cookies)
return json.loads(response)
def name_v1(id):
urls = "https://interface3.music.163.com/api/v3/song/detail"
data = {'c': json.dumps([{"id":id,"v":0}])}
response = requests.post(url=urls, data=data)
return response.json()
def lyric_v1(id, cookies):
url = "https://interface3.music.163.com/api/song/lyric"
data = {'id': id, 'cp': 'false', 'tv': '0', 'lv': '0', 'rv': '0', 'kv': '0', 'yv': '0', 'ytv': '0', 'yrv': '0'}
response = requests.post(url=url, data=data, cookies=cookies)
return response.json()
def search_music(keywords, cookies, limit=10):
"""
网易云音乐搜索接口,返回歌曲信息列表
:param keywords: 搜索关键词
:param cookies: 登录 cookies
:param limit: 返回数量
:return: 歌曲信息列表
"""
url = 'https://music.163.com/api/cloudsearch/pc'
data = {'s': keywords, 'type': 1, 'limit': limit}
headers = {
'User-Agent': 'Mozilla/5.0',
'Referer': 'https://music.163.com/'
}
response = requests.post(url, data=data, headers=headers, cookies=cookies)
result = response.json()
songs = []
for item in result.get('result', {}).get('songs', []):
song_info = {
'id': item['id'],
'name': item['name'],
'artists': '/'.join(artist['name'] for artist in item['ar']),
'album': item['al']['name'],
'picUrl': item['al']['picUrl']
}
songs.append(song_info)
return songs
def playlist_detail(playlist_id, cookies):
"""
获取网易云歌单详情及全部歌曲列表
:param playlist_id: 歌单ID
:param cookies: 登录 cookies
:return: 歌单基本信息和全部歌曲列表
"""
url = f'https://music.163.com/api/v6/playlist/detail'
data = {'id': playlist_id}
headers = {
'User-Agent': 'Mozilla/5.0',
'Referer': 'https://music.163.com/'
}
response = requests.post(url, data=data, headers=headers, cookies=cookies)
result = response.json()
playlist = result.get('playlist', {})
info = {
'id': playlist.get('id'),
'name': playlist.get('name'),
'coverImgUrl': playlist.get('coverImgUrl'),
'creator': playlist.get('creator', {}).get('nickname', ''),
'trackCount': playlist.get('trackCount'),
'description': playlist.get('description', ''),
'tracks': []
}
# 获取所有trackIds
track_ids = [str(t['id']) for t in playlist.get('trackIds', [])]
# 分批获取详细信息每批最多100首
for i in range(0, len(track_ids), 100):
batch_ids = track_ids[i:i+100]
song_detail_url = 'https://interface3.music.163.com/api/v3/song/detail'
song_data = {'c': json.dumps([{ 'id': int(sid), 'v': 0 } for sid in batch_ids])}
song_resp = requests.post(url=song_detail_url, data=song_data, headers=headers, cookies=cookies)
song_result = song_resp.json()
for song in song_result.get('songs', []):
info['tracks'].append({
'id': song['id'],
'name': song['name'],
'artists': '/'.join(artist['name'] for artist in song['ar']),
'album': song['al']['name'],
'picUrl': song['al']['picUrl']
})
return info
def album_detail(album_id, cookies):
"""
获取网易云专辑详情及全部歌曲列表
:param album_id: 专辑ID
:param cookies: 登录 cookies
:return: 专辑基本信息和全部歌曲列表
"""
url = f'https://music.163.com/api/v1/album/{album_id}'
headers = {
'User-Agent': 'Mozilla/5.0',
'Referer': 'https://music.163.com/'
}
response = requests.get(url, headers=headers, cookies=cookies)
result = response.json()
album = result.get('album', {})
info = {
'id': album.get('id'),
'name': album.get('name'),
'coverImgUrl': get_pic_url(album.get('pic')),
#'coverImgEncryptId': netease_encryptId(str(album.get('pic'))),
'artist': album.get('artist', {}).get('name', ''),
'publishTime': album.get('publishTime'),
'description': album.get('description', ''),
'songs': []
}
for song in result.get('songs', []):
info['songs'].append({
'id': song['id'],
'name': song['name'],
'artists': '/'.join(artist['name'] for artist in song['ar']),
'album': song['al']['name'],
'picUrl': get_pic_url(song['al'].get('pic'))
})
return info
def netease_encryptId(id_str):
"""
网易云加密图片ID算法PHP移植版
:param id_str: 歌曲/专辑/图片ID字符串
:return: 加密后的字符串
"""
import base64
magic = list('3go8&$8*3*3h0k(2)2')
song_id = list(id_str)
for i in range(len(song_id)):
song_id[i] = chr(ord(song_id[i]) ^ ord(magic[i % len(magic)]))
m = ''.join(song_id)
import hashlib
md5_bytes = hashlib.md5(m.encode('utf-8')).digest()
result = base64.b64encode(md5_bytes).decode('utf-8')
result = result.replace('/', '_').replace('+', '-')
return result
def get_pic_url(pic_id, size=300):
"""
获取网易云加密歌曲/专辑封面直链
:param pic_id: 封面ID数字或字符串
:param size: 图片尺寸默认300
:return: {'url': url}
"""
enc_id = netease_encryptId(str(pic_id))
url = f'https://p3.music.126.net/{enc_id}/{pic_id}.jpg?param={size}y{size}'
return url