python实现扫码登录网易云音乐网页版

  • 一、打开二维码扫码登录页面,找到二维码图片链接
  • 二、破解获取params和encSecKey值
    • 注意:from Crypto.Cipher import AES
  • 三、进行扫码操作,找到确认链接
  • 四、判断cookie是否有效
  • 五、其他文件
  • 六、保存cookies值并进行验证完整代码
    • 完整代码
  • 七 、 更多文章

一、打开二维码扫码登录页面,找到二维码图片链接

  • 本篇幅度比较长,有点难度,不过经过小编反复测试,终于找到规律完成扫码操作和获取登录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('已确认,登入成功!')breakelse: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, Trueelse:print('Cookies值已经失效,请重新扫码登录!')return session, Falseexcept: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 += readlineelse:breakreturn jst
def getjs():return djs('jsdm.js')# 获取ptqrtoken
def ptqrtoken(qrsign):# 加载jsexecjs_execjs = execjs.compile(getjs())return execjs_execjs.call('hash33', qrsign)
# 获取UI
def guid():# 加载jsexecjs_execjs = execjs.compile(getjs())return execjs_execjs.call('guid')
# 获取g_tk
def get_g_tk(p_skey):# 加载jsexecjs_execjs = execjs.compile(getjs())return execjs_execjs.call('getToken', p_skey)
# 获取i
def S():# 加载jsexecjs_execjs = execjs.compile(getjs())return execjs_execjs.call('S')
# 获取key
def a():# 加载jsexecjs_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 = datadef 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 encryptdef 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:passcsrf_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, Trueelse:print('Cookies值已经失效,请重新扫码登录!')return session, Falseexcept: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('已确认,登入成功!')breakelse:print('其他:', qrcodedata)time.sleep(2)with open('wyycookies.cookie', 'wb') as f:pickle.dump(session.cookies, f)return sessionif __name__ == '__main__':wyylogin()
  • 网易云音乐每个链接网址访问都需要params和encSecKey值这两个值,所以获取MP3地址也需要这两个值,只是访问链接和D值参数不一样,后期小编会开设采集数据专栏包括音乐视频等数据!

七 、 更多文章

  1. 抖音篇(一)
  2. 快手篇(二)
  3. 微视篇(三)
  4. 微信公众号篇(四)
  5. 微博篇(五))
  6. B站篇(六))
  7. CSDN篇(八))