python实现扫码登录网易云音乐网页版
一、打开二维码扫码登录页面,找到二维码图片链接
- 本篇幅度比较长,有点难度,不过经过小编反复测试,终于找到规律完成扫码操作和获取登录cookie信息。稍微加了一些图片来说明吧。
- 先找到生成二维码的链接,打开登录页面:https://music.163.com/#/login
- 点击二维码右键鼠标,选择检查选项,每个浏览器不一样,小编用的是谷歌浏览器,如下图:
- 找到了二维码链接:http://music.163.com/login?codekey=9582af31-7ac9-4863-a83b-0764b11e08e3
- 发现变量参数codekey,下一步就去获取
- 通过:https://music.163.com/weapi/login/qrcode/unikey?csrf_token=。这个可以获取unikey值
二、破解获取params和encSecKey值
- 上面我们知道了获取unikey值的链接,但是访问此链接必须要有两个参数params和encSecKey值
- 要获取这两个参数,小编花费不少时间~
- 搜索其中一个参数encSecKey,双击下面链接
- 优化js代码,CTRL+F继续进行搜索encSecKey值,找到这两个值生成方式
- 通过上面代码可以看出我们需要知道bWv7o的值,这个值由下面这个四组合的
(JSON.stringify(i6c), bsK7D(["流泪", "强"]), bsK7D(XR0x.md), bsK7D(["爱心", "女孩", "惊恐", "大笑"])
- 首先获取i6c的值,通过断点可以知道
i6c: {key: "1b601cd2-960c-4ee5-b1c6-e90dcc5a3f59", type: "1", csrf_token: ""}
- 再者**bsK7D([“流泪”, “强”])**的值,同上面方法,点击红色块跳另一个地方,进行断点运行
Return value: "010001"
- 然后**bsK7D(XR0x.md)**的值,再点击运行,就可以出现Return value值
Return value: "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
- 最后一个值bsK7D([“爱心”, “女孩”, “惊恐”, “大笑”]
Return value: "0CoJUm6Qyw8W8jud"
- 四个值获取到了
- i6c: {key: “1b601cd2-960c-4ee5-b1c6-e90dcc5a3f59”, type: “1”, csrf_token: “”}
- Return value: "010001"
- Return value: "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
- Return value:"0CoJUm6Qyw8W8jud"
- key值是变量值,其他值都是固定值,下面在vipcashier.umd.js中找到获取key值的方法,就是时间加随机加固定值组合而成
function S() {
var e = (new Date).getTime();
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(t) {
var n = (e + 16 * Math.random()) % 16 | 0;
return e = Math.floor(e / 16),
("x" === t ? n : 7 & n | 8).toString(16)
})
}
-上面四个参数获取到了,还有一个大包围window.asrsea
var bWv7o = window.asrsea(JSON.stringify(i6c), bsK7D(["流泪", "强"]), bsK7D(XR0x.md), bsK7D(["爱心", "女孩", "惊恐", "大笑"]));
- d,e,f,g就是上面那四个参数的按步骤获取的参数,这里断点运行之后直接就可以看到
d: "{"key":"1b601cd2-960c-4ee5-b1c6-e90dcc5a3f59","type":"1","csrf_token":""}"
e: "010001"
f: "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g: "0CoJUm6Qyw8W8jud"
- 通过上面这段代码可以看出,这里又继续加密了
进一步化简:
i = a(16)
h.encText = b(d, g)
h.encText = b(h.encText, i)
h.encSecKey = c(i, e, f)
大概就这个意思
encText = b(b(d, g), i)
encSecKey = c(i, e, f)
- d, e, f, g的值我们获取到了,下面首先获取 i 值
- 获取 i 值:i = a(16),同上方法进行寻找
- i值获取方式,将i = a(16)带入该函数单独随机生成,没其他函数参与
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
}
可以看出这就是由这段代码随机生成的16位值
- 接着就是b(b(d, g), i),这个函数AES加密的
- 从上面可以看出iv=0102030405060708,这是偏移量,固定值
iv:0102030405060708
- 将a,b值带入函数中生成新的参数,是b(d, g),变成b(a, b),其实就是a就是d值,g就是b值
d: "{"key":"1b601cd2-960c-4ee5-b1c6-e90dcc5a3f59","type":"1","csrf_token":""}"
g: "0CoJUm6Qyw8W8jud"
- 将b(b(d, g), i)函数代码转换成python代码,b函数加密了两次
def keys(key):
while len(key) % 16 != 0:
key += '\0'
return str.encode(key)
#AES解密
def AES_aes(t, key, iv):
p = lambda s: s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)
encrypt = str(base64.encodebytes(AES.new(keys(key), AES.MODE_CBC,keys(iv)).encrypt(str.encode(p(t)))), encoding='utf-8')
return encrypt
#将上面参数d, g, iv带入其中就行
def params(): #b(b(d, g), i)和下面是不是很相似
return AES_aes(AES_aes(d, g, iv), i, iv) #g 和 i 都是key代替
- 再接着就是:encSecKey = c(i, e, f),同上办法,其实就在上面这段
- 这个比较简单,加了一次密是RSA加密,把它转换成python代码
- 把 i, e, f 参数带入其运行就行
def RSA_rsa(i, e, f):
return format(int(codecs.encode(i[::-1].encode('utf-8'), 'hex_codec'), 16) ** int(e, 16) % int(f, 16), 'x').zfill(256)
def encSecKey():
return RSA_rsa(i, e, f)
- 到此这两个参数值就算出来了
def params():
return AES_aes(AES_aes(d, g, iv), i, iv)
def encSecKey():
return RSA_rsa(i, e, f)
注意:from Crypto.Cipher import AES
- 小编再使用crypto库的时候,老是显示错误,最后再网上找到解决办法
解决方法:
安装crypto后,将\venv\Lib\site-packages\crypto的crypto文件夹名首字母c改成大写C就可以了
三、进行扫码操作,找到确认链接
- 上面我们找到生成二维码的链接和params、encSecKey两个参数,下面就进行访问
#访问生成二维码网址的d值是这样的,其他不变,下面的确认网址的d值有所不一样
#key上面已经说了,这里的key值是随机生成的
d = str({'key': key, 'type': "1", 'csrf_token': ""})
def params():
return AES_aes(AES_aes(d, g, iv), i, iv)
def encSecKey():
return RSA_rsa(i, e, f)
getlogin = session.post('https://music.163.com/weapi/login/qrcode/unikey?csrf_token=', data={'params': params(), 'encSecKey': encSecKey()}, headers=headers).json()
#输出getlogin = {'code': 200, 'unikey': '3d99……'}
- 通过识别登录页面二维码的隐藏文字信息,是这样的规则
https://music.163.com/login?codekey=52f86249-fa48-4914-a324-0df788108e05&refer=scan
- 所以codekey就是unikey值,接着就是把unikey带入该网址用python的qrcode库转换成二维码进行扫码操作
pngurl = 'https://music.163.com/login?codekey='+getlogin['unikey']+'&refer=scan'
qr = qrcode.QRCode()
qr.add_data(pngurl)
img = qr.make_image()
# 缓存的好处就是不需要保存本地
a = BytesIO()
img.save(a, 'png')
png = a.getvalue()
a.close()
# 打开二维码进行扫码操作
t = showpng(png)
t.start()
- 接着找到确认网址,进行测试登录,确认网址一般输出都是:等待扫码、未扫码等字样,比较好找,下面找到以后进行登录测试结果
tokenurl = 'https://music.163.com/weapi/login/qrcode/client/login?csrf_token='
while 1:
u = str({'key': getlogin['unikey'], 'type': "1", 'csrf_token': ""})
qrcodedata = session.post(tokenurl, data={'params': AES_aes(AES_aes(u, g, iv), i, iv), 'encSecKey': encSecKey()}, headers=headers).json()
if '801' in str(qrcodedata['code']):
print('二维码未失效,请扫码!')
elif '802' in str(qrcodedata['code']):
print('已扫码,请确认!')
elif '803' in str(qrcodedata['data']['code']):
print('已确认,登入成功!')
break
else:
print('其他:', qrcodedata)
time.sleep(2)
- 上面小编说过,生成二维码网址和确认网址的d值不一样,这我用u来代替,这里小编猜测了很久,测试很久,才成功的。其实也很简单,就是这里的key不是随机的了,而是unikey值,最后成功登录。
- 登录成功之后就获取了cookie,没其他网址链接了。
四、判断cookie是否有效
- 这里小编页研究了很久,发现登录以后每个网址后面都带有
csrf_token=53b3e139d912……
- csrf_token值就是登录以后获取的cookie值,小编就将csrf_token值从cookie提取出来,放入网址进行判断
def islogin(session):
try:
session.cookies.load(ignore_discard=True)
except Exception:
pass
#从cookie提取csrf_token值
csrf_token = session.cookies.get('__csrf')
c = str({'csrf_token': csrf_token})
try:
loginurl = session.post('https://music.163.com/weapi/w/nuser/account/get?csrf_token={}'.format(csrf_token), data={'params': AES_aes(AES_aes(c, g, iv), i, iv), 'encSecKey': encSecKey()}, headers=headers).json()
if '200' in str(loginurl['code']):
print('Cookies值有效:',loginurl['profile']['nickname'],',已登录!')
return session, True
else:
print('Cookies值已经失效,请重新扫码登录!')
return session, False
except:
print('Cookies值已经失效,请重新扫码登录!')
return session, False
- 该网址的d也所变化,小编用c来代替,就放csrf_token值了,上面几个访问链接不需要放。如果还没登录,cookie就没有,就提不了csrf_token值,就报错不运行了,所以小编就使用tuy双判断进行,测试成功!
五、其他文件
- 要运行本篇的完整代码,就需要这两个文件agent.py和jsdm.js,同下面完整代码文件放同一根目录就行。
- agent.py文件完整代码。这里除了和这篇有关,前几篇文章运行也需要这两个文件
# -*- coding: UTF-8 -*-
import random
import random
import execjs
agent = [
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
#可以多添加几个
]
# 获取浏览器认证头
def get_user_agents():
return random.choice(agent)
# 读取js
def djs(js):
f = open(js, 'r', encoding='utf-8')
jst = ''
while True:
readline = f.readline()
if readline:
jst += readline
else:
break
return jst
def getjs():
return djs('jsdm.js')
# 获取ptqrtoken
def ptqrtoken(qrsign):
# 加载js
execjs_execjs = execjs.compile(getjs())
return execjs_execjs.call('hash33', qrsign)
# 获取UI
def guid():
# 加载js
execjs_execjs = execjs.compile(getjs())
return execjs_execjs.call('guid')
# 获取g_tk
def get_g_tk(p_skey):
# 加载js
execjs_execjs = execjs.compile(getjs())
return execjs_execjs.call('getToken', p_skey)
# 获取i
def S():
# 加载js
execjs_execjs = execjs.compile(getjs())
return execjs_execjs.call('S')
# 获取key
def a():
# 加载js
execjs_execjs = execjs.compile(getjs())
return execjs_execjs.call('a', 16)
- jsdm.js 文件完整代码
function hash33(t) {
for (var e = 0, i = 0, n = t.length; i < n; ++i)
e += (e << 5) + t.charCodeAt(i);
return 2147483647 & e
}
function getToken(p_skey) {
var str = p_skey || '',
hash = 5381;
for (var i = 0, len = str.length; i < len; ++i) {
hash += (hash << 5) + str.charCodeAt(i);
}
return hash & 0x7fffffff;
}
function guid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
}).toUpperCase();
};
function S() {
var e = (new Date).getTime();
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(t) {
var n = (e + 16 * Math.random()) % 16 | 0;
return e = Math.floor(e / 16),
("x" === t ? n : 7 & n | 8).toString(16)
})
}
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
}
六、保存cookies值并进行验证完整代码
完整代码
# -*- coding: utf-8 -*-
import base64
import codecs
import pickle
from Crypto.Cipher import AES
#解决方法:安装crypto后,将\venv\Lib\site-packages\crypto的crypto文件夹名首字母c改成大写C就可以了
import qrcode
import agent
from threading import Thread
import time
import requests
from io import BytesIO
from PIL import Image
import os
requests.packages.urllib3.disable_warnings()
headers = {'User-Agent': agent.get_user_agents(),'Referer':'https://music.163.com/'}
class showpng(Thread):
def __init__(self, data):
Thread.__init__(self)
self.data = data
def run(self):
img = Image.open(BytesIO(self.data))
img.show()
#解密params和encSecKey值
def keys(key):
while len(key) % 16 != 0:
key += '\0'
return str.encode(key)
def AES_aes(t, key, iv):
p = lambda s: s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)
encrypt = str(base64.encodebytes(AES.new(keys(key), AES.MODE_CBC,keys(iv)).encrypt(str.encode(p(t)))), encoding='utf-8')
return encrypt
def RSA_rsa(i, e, f):
return format(int(codecs.encode(i[::-1].encode('utf-8'), 'hex_codec'), 16) ** int(e, 16) % int(f, 16), 'x').zfill(256)
#获取的参数
key = agent.S() # i6c的值
d = str({'key': key, 'type': "1", 'csrf_token': ""})
e = "010001"# (["流泪", "强"])的值
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud" # (["爱心", "女孩", "惊恐", "大笑"])的值
iv = "0102030405060708" # 偏移量
i = agent.a() # 随机生成长度为16的字符串
def params():
return AES_aes(AES_aes(d, g, iv), i, iv) #g 和 i 都是key代替
def encSecKey():
return RSA_rsa(i, e, f)
#判断cookie是否有效
def islogin(session):
try:
session.cookies.load(ignore_discard=True)
except Exception:
pass
csrf_token = session.cookies.get('__csrf')
c = str({'csrf_token': csrf_token})
try:
loginurl = session.post('https://music.163.com/weapi/w/nuser/account/get?csrf_token={}'.format(csrf_token), data={'params': AES_aes(AES_aes(c, g, iv), i, iv), 'encSecKey': encSecKey()}, headers=headers).json()
if '200' in str(loginurl['code']):
print('Cookies值有效:',loginurl['profile']['nickname'],',已登录!')
return session, True
else:
print('Cookies值已经失效,请重新扫码登录!')
return session, False
except:
print('Cookies值已经失效,请重新扫码登录!')
return session, False
#登录扫码保存cookie
def wyylogin():
# 写入
session = requests.session()
if not os.path.exists('wyycookies.cookie'):
with open('wyycookies.cookie', 'wb') as f:
pickle.dump(session.cookies, f)
# 读取
session.cookies = pickle.load(open('wyycookies.cookie', 'rb'))
session, status = islogin(session)
if not status:
getlogin = session.post('https://music.163.com/weapi/login/qrcode/unikey?csrf_token=', data={'params': params(), 'encSecKey': encSecKey()}, headers=headers).json()
pngurl = 'https://music.163.com/login?codekey=' + getlogin['unikey'] + '&refer=scan'
qr = qrcode.QRCode()
qr.add_data(pngurl)
img = qr.make_image()
# 缓存的好处就是不需要保存本地
a = BytesIO()
img.save(a, 'png')
png = a.getvalue()
a.close()
# 打开二维码进行扫码操作
t = showpng(png)
t.start()
tokenurl = 'https://music.163.com/weapi/login/qrcode/client/login?csrf_token='
while 1:
u = str({'key': getlogin['unikey'], 'type': "1", 'csrf_token': ""})
qrcodedata = session.post(tokenurl, data={'params': AES_aes(AES_aes(u, g, iv), i, iv), 'encSecKey': encSecKey()}, headers=headers).json()
if '801' in str(qrcodedata['code']):
print('二维码未失效,请扫码!')
elif '802' in str(qrcodedata['code']):
print('已扫码,请确认!')
elif '803' in str(qrcodedata['code']):
print('已确认,登入成功!')
break
else:
print('其他:', qrcodedata)
time.sleep(2)
with open('wyycookies.cookie', 'wb') as f:
pickle.dump(session.cookies, f)
return session
if __name__ == '__main__':
wyylogin()
- 网易云音乐每个链接网址访问都需要params和encSecKey值这两个值,所以获取MP3地址也需要这两个值,只是访问链接和D值参数不一样,后期小编会开设采集数据专栏包括音乐视频等数据!
七 、 更多文章
- 后期小编将开设登录后批量采集各平台数据(点赞、播放量、评论、图片、视频、音乐等)专栏文章!记得关注哟!😜
- 如果文章能帮到您,愿意给小编点个 赞 👍 吗,么么哒~😘 (●’◡’●)