Skip to content

Commit f9ba8e9

Browse files
authored
Add files via upload
1 parent e8c1334 commit f9ba8e9

File tree

1 file changed

+236
-0
lines changed

1 file changed

+236
-0
lines changed

NetEese/NetEase.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# -*- coding:utf-8 -*-
2+
import requests, hashlib, sys, click, re, base64, binascii, json, os
3+
from Crypto.Cipher import AES
4+
from http import cookiejar
5+
6+
"""
7+
Website:http://cuijiahua.com
8+
Author:Jack Cui
9+
Refer:https://github.com/darknessomi/musicbox
10+
"""
11+
12+
class Encrypyed():
13+
"""
14+
解密算法
15+
"""
16+
def __init__(self):
17+
self.modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
18+
self.nonce = '0CoJUm6Qyw8W8jud'
19+
self.pub_key = '010001'
20+
21+
# 登录加密算法, 基于https://github.com/stkevintan/nw_musicbox脚本实现
22+
def encrypted_request(self, text):
23+
text = json.dumps(text)
24+
sec_key = self.create_secret_key(16)
25+
enc_text = self.aes_encrypt(self.aes_encrypt(text, self.nonce), sec_key.decode('utf-8'))
26+
enc_sec_key = self.rsa_encrpt(sec_key, self.pub_key, self.modulus)
27+
data = {'params': enc_text, 'encSecKey': enc_sec_key}
28+
return data
29+
30+
def aes_encrypt(self, text, secKey):
31+
pad = 16 - len(text) % 16
32+
text = text + chr(pad) * pad
33+
encryptor = AES.new(secKey.encode('utf-8'), AES.MODE_CBC, b'0102030405060708')
34+
ciphertext = encryptor.encrypt(text.encode('utf-8'))
35+
ciphertext = base64.b64encode(ciphertext).decode('utf-8')
36+
return ciphertext
37+
38+
def rsa_encrpt(self, text, pubKey, modulus):
39+
text = text[::-1]
40+
rs = pow(int(binascii.hexlify(text), 16), int(pubKey, 16), int(modulus, 16))
41+
return format(rs, 'x').zfill(256)
42+
43+
def create_secret_key(self, size):
44+
return binascii.hexlify(os.urandom(size))[:16]
45+
46+
47+
class Song():
48+
"""
49+
歌曲对象,用于存储歌曲的信息
50+
"""
51+
def __init__(self, song_id, song_name, song_num, song_url=None):
52+
self.song_id = song_id
53+
self.song_name = song_name
54+
self.song_num = song_num
55+
self.song_url = '' if song_url is None else song_url
56+
57+
class Crawler():
58+
"""
59+
网易云爬取API
60+
"""
61+
def __init__(self, timeout=60, cookie_path='.'):
62+
self.headers = {
63+
'Accept': '*/*',
64+
'Accept-Encoding': 'gzip,deflate,sdch',
65+
'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4',
66+
'Connection': 'keep-alive',
67+
'Content-Type': 'application/x-www-form-urlencoded',
68+
'Host': 'music.163.com',
69+
'Referer': 'http://music.163.com/search/',
70+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
71+
}
72+
self.session = requests.Session()
73+
self.session.headers.update(self.headers)
74+
self.session.cookies = cookiejar.LWPCookieJar(cookie_path)
75+
self.download_session = requests.Session()
76+
self.timeout = timeout
77+
self.ep = Encrypyed()
78+
79+
def post_request(self, url, params):
80+
"""
81+
Post请求
82+
:return: 字典
83+
"""
84+
85+
data = self.ep.encrypted_request(params)
86+
resp = self.session.post(url, data=data, timeout=self.timeout)
87+
result = resp.json()
88+
if result['code'] != 200:
89+
click.echo('post_request error')
90+
else:
91+
return result
92+
93+
def search(self, search_content, search_type, limit=9):
94+
"""
95+
搜索API
96+
:params search_content: 搜索内容
97+
:params search_type: 搜索类型
98+
:params limit: 返回结果数量
99+
:return: 字典.
100+
"""
101+
102+
url = 'http://music.163.com/weapi/cloudsearch/get/web?csrf_token='
103+
params = {'s': search_content, 'type': search_type, 'offset': 0, 'sub': 'false', 'limit': limit}
104+
result = self.post_request(url, params)
105+
return result
106+
107+
def search_song(self, song_name, song_num, quiet=True, limit=9):
108+
"""
109+
根据音乐名搜索
110+
:params song_name: 音乐名
111+
:params song_num: 下载的歌曲数
112+
:params quiet: 自动选择匹配最优结果
113+
:params limit: 返回结果数量
114+
:return: Song独享
115+
"""
116+
117+
result = self.search(song_name, search_type=1, limit=limit)
118+
119+
if result['result']['songCount'] <= 0:
120+
click.echo('Song {} not existed.'.format(song_name))
121+
else:
122+
songs = result['result']['songs']
123+
if quiet:
124+
song_id, song_name = songs[0]['id'], songs[0]['name']
125+
song = Song(song_id=song_id, song_name=song_name, song_num=song_num)
126+
return song
127+
128+
def get_song_url(self, song_id, bit_rate=320000):
129+
"""
130+
获得歌曲的下载地址
131+
:params song_id: 音乐ID<int>.
132+
:params bit_rate: {'MD 128k': 128000, 'HD 320k': 320000}
133+
:return: 歌曲下载地址
134+
"""
135+
136+
url = 'http://music.163.com/weapi/song/enhance/player/url?csrf_token='
137+
csrf = ''
138+
params = {'ids': [song_id], 'br': bit_rate, 'csrf_token': csrf}
139+
result = self.post_request(url, params)
140+
# 歌曲下载地址
141+
song_url = result['data'][0]['url']
142+
143+
# 歌曲不存在
144+
if song_url is None:
145+
click.echo('Song {} is not available due to copyright issue.'.format(song_id))
146+
else:
147+
return song_url
148+
149+
def get_song_by_url(self, song_url, song_name, song_num, folder):
150+
"""
151+
下载歌曲到本地
152+
:params song_url: 歌曲下载地址
153+
:params song_name: 歌曲名字
154+
:params song_num: 下载的歌曲数
155+
:params folder: 保存路径
156+
"""
157+
if not os.path.exists(folder):
158+
os.makedirs(folder)
159+
fpath = os.path.join(folder, str(song_num) + '_' + song_name + '.mp3')
160+
if sys.platform == 'win32' or sys.platform == 'cygwin':
161+
valid_name = re.sub(r'[<>:"/\\|?*]', '', song_name)
162+
if valid_name != song_name:
163+
click.echo('{} will be saved as: {}.mp3'.format(song_name, valid_name))
164+
fpath = os.path.join(folder, str(song_num) + '_' + valid_name + '.mp3')
165+
166+
if not os.path.exists(fpath):
167+
resp = self.download_session.get(song_url, timeout=self.timeout, stream=True)
168+
length = int(resp.headers.get('content-length'))
169+
label = 'Downloading {} {}kb'.format(song_name, int(length/1024))
170+
171+
with click.progressbar(length=length, label=label) as progressbar:
172+
with open(fpath, 'wb') as song_file:
173+
for chunk in resp.iter_content(chunk_size=1024):
174+
if chunk:
175+
song_file.write(chunk)
176+
progressbar.update(1024)
177+
178+
179+
class NetEase():
180+
"""
181+
网易云音乐下载
182+
"""
183+
def __init__(self, timeout, folder, quiet, cookie_path):
184+
self.crawler = Crawler(timeout, cookie_path)
185+
self.folder = '.' if folder is None else folder
186+
self.quiet = quiet
187+
188+
def download_song_by_search(self, song_name, song_num):
189+
"""
190+
根据歌曲名进行搜索
191+
:params song_name: 歌曲名字
192+
:params song_num: 下载的歌曲数
193+
"""
194+
195+
try:
196+
song = self.crawler.search_song(song_name, song_num, self.quiet)
197+
except:
198+
click.echo('download_song_by_serach error')
199+
# 如果找到了音乐, 则下载
200+
if song != None:
201+
self.download_song_by_id(song.song_id, song.song_name, song.song_num, self.folder)
202+
203+
def download_song_by_id(self, song_id, song_name, song_num, folder='.'):
204+
"""
205+
通过歌曲的ID下载
206+
:params song_id: 歌曲ID
207+
:params song_name: 歌曲名
208+
:params song_num: 下载的歌曲数
209+
:params folder: 保存地址
210+
"""
211+
try:
212+
url = self.crawler.get_song_url(song_id)
213+
# 去掉非法字符
214+
song_name = song_name.replace('/', '')
215+
song_name = song_name.replace('.', '')
216+
self.crawler.get_song_by_url(url, song_name, song_num, folder)
217+
218+
except:
219+
click.echo('download_song_by_id error')
220+
221+
222+
if __name__ == '__main__':
223+
timeout = 60
224+
output = 'Musics'
225+
quiet = True
226+
cookie_path = 'Cookie'
227+
netease = NetEase(timeout, output, quiet, cookie_path)
228+
music_list_name = 'music_list.txt'
229+
# 如果music列表存在, 那么开始下载
230+
if os.path.exists(music_list_name):
231+
with open(music_list_name, 'r') as f:
232+
music_list = list(map(lambda x: x.strip(), f.readlines()))
233+
for song_num, song_name in enumerate(music_list):
234+
netease.download_song_by_search(song_name,song_num + 1)
235+
else:
236+
click.echo('music_list.txt not exist.')

0 commit comments

Comments
 (0)