赞
踩
这一篇接着上一篇继续写。在上一篇里,介绍了歌曲的 查找 功能和代码实现,这里继续介绍 播放 功能,那各位观众姥爷一起来看下吧。
我们打开下面这个网页
https://music.163.com/#/song?id=444267215
先分析下这个链接地址,我们发现只有一个参数,就是 id ,也就是每首歌特有的id
接着,我们在本页面按 F12 调出调试页面,选择 network 然后点击一次 播放
开始分析,发现,系统向这个链接发送post请求
也就是这个链接
https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=
返回的json中包含歌曲的播放链接:
经过分析,我们发现他有两个参数
params:
encSecKey:
我们上面说了,现在只知道一个参数 id ,所以初步分析,这两个参数应该经过加密
我们在调试页面选择 Sources 查找js代码,你可以在全局查找上面提到的两个参数:params、encSecKey,可
以发现,在 core 文件中(如果发现没有出现这个文件,可以多刷新几次页面,原因我也不太清楚)
中发现,encSecKey 一共有三个,可以点击左下角的 {} 可以代码规范化,就不会乱糟糟的,我们发现
这几句代码,params、encSecKey 都在一个叫 bXY4c 的参数中获取,而 bXY4c 是经过一个叫
window.asrsea 的函数获取,然而这个函数一共有四个参数,我们一一分析。
首先,我们在这里打上断点:
然后点击 播放,会发现程序在这里停下来,再点击下一步,我们开始分析:
分别复制 **JSON.stringify(i2x), bqu6o([“流泪”, “强”]), bqu6o(QE6y.md), bqu6o([“爱心”, “女孩”, “惊恐”, “大笑”])**这
几个参数,在 Console 页面打印,查看值
经过多次的测试发现,
bqu6o([“流泪”, “强”]), bqu6o(QE6y.md), bqu6o([“爱心”, “女孩”, “惊恐”, “大笑”]
这几个值是固定值,主要的是 JSON.stringify(i2x) 为不固定的,我们来看下他的格式
JSON.stringify(i2x) = {
'csrf_token': "",
'encodeType': "aac",
'ids': "[444267215]",
'level': "standard"
}
一眼就能看出来,ids就是歌曲的id,其他的参数现在不知道是什么,不过,可以先尝试请求,如果可以就不用再折腾啦,如果不行,就继续分析。
接着,我们看下window.asrsea 这个函数,将鼠标放在上面,点击链接
我们看到下面的两句代码:
window.asrsea = d,
window.ecnonasr = e
得知,window.asrsea 这个函数就是 d 函数,现在参数也知道了
我们来看下代码
!function() { function a(a) { var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = ""; for (d = 0; a > d; d += 1) e = Math.random() * b.length, e = Math.floor(e), c += b.charAt(e); return c } function b(a, b) { var c = CryptoJS.enc.Utf8.parse(b) , d = CryptoJS.enc.Utf8.parse("0102030405060708") , e = CryptoJS.enc.Utf8.parse(a) , f = CryptoJS.AES.encrypt(e, c, { iv: d, mode: CryptoJS.mode.CBC }); return f.toString() } function c(a, b, c) { var d, e; return setMaxDigits(131), d = new RSAKeyPair(b,"",c), e = encryptedString(d, a) } function d(d, e, f, g) { var h = {} , i = a(16); return h.encText = b(d, g), h.encText = b(h.encText, i), h.encSecKey = c(i, e, f), h } function e(a, b, d, e) { var f = {}; return f.encText = c(a + e, b, d), f } window.asrsea = d, window.ecnonasr = e }();
可以发现,在函数 d 中,对两个参数进行赋值,而其中,a调用一次,b函数调用2次,c函数调用1次,由于楼主js基础不太好(现在在恶补),只能得知:
a函数传一个int,可以获取这个参数长度的随机字符串
b函数是某种加密手段(百度得知为AES加密)并且加密了两次
c函数也是某种加密手段,只加密一次(有大佬说,这个值可以是固定值,但是我尝试过,并不能通过)
其中,
params 由两次b函数产出
encSecKey 由c函数产出
而,两个函数都经过 a 函数,大概思路理清楚,现在可以开始用python重写。
我自己写的代码太乱了,这里参考下大佬的代码,干净整洁
import os,json from binascii import hexlify from Crypto.Cipher import AES import base64 class Encrypyed(): def __init__(self): # 加密的固有参数 self.pub_key = "010001" self.modulus = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff6" \ "8ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee34" \ "1f56135fccf695280104e0312ecbda92557c93870114af6c9d05c" \ "4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e820" \ "47b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7" self.nonce = "0CoJUm6Qyw8W8jud" # 随机产生16位参数 def a(self, size): return hexlify(os.urandom(size))[:16].decode('utf-8') # 加密 def b(self,text, key): iv = '0102030405060708' pad = 16 - len(text) % 16 text = text + pad * chr(pad) encryptor = AES.new(key, AES.MODE_CBC, iv) result = encryptor.encrypt(text) result_str = base64.b64encode(result).decode('utf-8') return result_str # 产生第二个参数 def c(self, text, pubKey, modulus): text = text[::-1] rs = pow(int(hexlify(text.encode('utf-8')), 16), int(pubKey, 16), int(modulus, 16)) return format(rs, 'x').zfill(256) # 赋值加密 def d(self, text): text = json.dumps(text) i = self.a(16) encText = self.b(text, self.nonce) encText = self.b(encText,i) encSecKey = self.c(i,self.pub_key,self.modulus) data = {'params': encText, 'encSecKey': encSecKey} return data
调用上面的代码,d函数是入口,将第一个参数传进去,就可以获取解密后的 params 、encSecKey ,对
https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=
进行post请求,就可以获取带有播放链接的json啦。
import requests from music import music_data as md from bs4 import BeautifulSoup import urllib def jiemi(): # 构造请求字典 query = { 'csrf_token': "", 'encodeType': "aac", 'ids': "[566521546]", 'level': "standard" } # 解密 do = md.Encrypyed() # 请求参数 data = do.d(query) print(data) # 开始请求 r = requests.session() # 请求头 headers = { 'origin': 'https: // music.163.com', 'referer': 'https: // music.163.com /', 'user - agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36' } # 请求url url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=' # 开始请求 html = r.post(url, data=data, headers=headers) # 只取播放链接 song_url = html.json()['data'][0]['url'] print(song_url) # print(html.json())
到这里,我们已经完成了解密以及下载,接下来就是界面的实现。
这里的界面使用 tkinter 库,比较简单,把功能实现了,但是界面有点简陋,可视化编程就不讲解了,直接贴代码
import tkinter as tk from tkinter import ttk from music import wyy_music as wyy import pygame import os from music import open_music as op # 搜索音乐 def file_music(): # 清除 delButton(treeview) print(inputText.get()) if inputText.get() != "": global data data = wyy.find_music(inputText.get()) # 计数器归零 i = 0 for music in data: # print(i) treeview.insert('', i, values=(i, music['b'], music['c'], music['time'])) i += 1 # 清空表单 def delButton(treeview): x = treeview.get_children() for item in x: treeview.delete(item) # 绑定事件 def treeviewClick(event): for item in treeview.selection(): item_text = treeview.item(item, "values") print(item_text[0]) # 输出所选行的第一列的值 # 获取歌曲id music_id = data[int(item_text[0])]['a'][9:] # print(byte_obj) # 对id进行判断,是否已经下载 find_mp3 = os.path.exists(r"my_music/"+music_id+'.mp3') if find_mp3: print('文件已经存在不用下载') else: print('正在下载...') op.login_music(music_id) pygame.mixer.music.load(r"my_music/"+music_id+".mp3") # 播放音乐 pygame.mixer.music.play() # 初始化播放器 pygame.mixer.init() # 启动浏览器 wyy.open_chrome() # 查找后的歌曲存放 data = {} # 初始化Tk() root = tk.Tk() root.title("音乐播放器V1.0") # 设置窗口标题 root.geometry("1100x600") # 设置窗口大小 注意:是x 不是* root.resizable(width=False, height=False) # 设置窗口是否可以变化长/宽,False不可变,True可变,默认为True # 设置输入框 inputText = tk.Entry(root, show=None, foreground='black', font=('Helvetica', '15', 'bold'), insertbackground='green', width=20) inputText.place(x=400, y=10,) # 设置按钮,以及放置的位置 searchBtn = tk.Button(root, text="搜索", fg="blue", bd=2, width=10, command=file_music) # command中的方法带括号是直接执行,不带括号才是点击执行 searchBtn.place(x=650, y=8, anchor='nw') update_progress = tk.StringVar() # 创建滚动条 scroll = tk.Scrollbar() columns = ("编号", "歌曲", "演唱者", "时长") treeview = ttk.Treeview(root, height=18, show="headings", columns=columns) # 表格 treeview.column("编号", width=100, anchor='center') # 表示列,不显示 treeview.column("歌曲", width=300, anchor='center') treeview.column("演唱者", width=300, anchor='center') treeview.column("时长", width=300, anchor='center') treeview.heading("编号", text="编号") # 显示表头 treeview.heading("歌曲", text="歌曲") treeview.heading("演唱者", text="演唱者") treeview.heading("时长", text="时长") # side放到窗体的哪一侧, fill填充 scroll.pack(side=tk.RIGHT, fill=tk.Y) treeview.pack(side=tk.LEFT, fill=tk.Y) # 关联 scroll.config(command=treeview.yview) treeview.config(yscrollcommand=scroll.set) treeview.pack() treeview.place(x=45, y=120,) # 双击触发 treeview.bind('<Double-Button-1>', treeviewClick) # 进入消息循环 root.mainloop()
这里有一个小小的插曲,我们抓取的链接,是 .m4p 结尾的,但是我找的播放库,都是不支持 .m4p,需要先进行转
码,这里比较麻烦,我也没有找到解决的办法,不知道各位大佬有没有建议。
播放的思路是先将歌曲下载到本地,然后再进行播放,如果遇到网速比较慢的可能有点延迟,还有,播放前会先进行一个
判断,如果本地已经有这个音乐就不会重新下载,直接播放。
经过百度处理 .m4p 无果后,偶然得知一个接口。
'http://music.163.com/song/media/outer/url?id='+music_id+'.mp3'
这个链接可以获取 mp3 音乐,id 依然是音乐的id,下载后可以直接用 pygame 库播放。
(哎呀…之前那些工作有点多余呀,不过一番下来后,了解了一些没触及的知识,还是有所收获)
好了,完整的代码就这样子,我会将它上传到我的 github 库,上传后供大家下载~
有疑问可以问我哦,最后祝大家敲码愉快。
https://github.com/1040230345/wyy_crawler.git
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。