参考链接:
解析网易云音乐的加密方式
https://www.jianshu.com/p/069e88181488

找到参数的加密方法

首先我们先看评论的加载方式,打开一首音乐的主页,然后打开开发工具的Network选项,点击评论的翻页按钮,可以看到第一个请求就是请求下一页的评论:

我们分析一下这个请求,先看它的url,请求多次之后发现R_SO_4_在请求评论时是固定的,483671599则是歌曲的id,url还有一个参数csrf_token,看这个名字像是防止跨站攻击的,但是它一直是空的。然后就是POST里面的参数params和encSecKey,这两个参数是关键,接下来我们要重点分析它。
我们在开发工具对encSecKey进行全局搜索,发现它只出现在一个文件中:

点击搜索结果,打开文件并美化后发现,这2处地方,一个只是简单对结果赋值,params通过bAQ8I.encText而来,encSecKey通过bAQ8I.encSecKey而来,而另一个则是有具体函数调用,而这个就是我们的突破口。

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
}

我们先简单分析一下这几个函数,可以看到最后的赋值是在d(d,e,f,g)这个函数内完成的,它首先调用了a(a),可以看出这个函数的作用是生成一个长度为16的随机字符串;然后encText这个参数通过2次调用b(a,b)完成,这个函数的作用是进行AES加密;最后encSecKey是调用c(i,e,f)完成,这个函数的作用是进行RSA加密。
通过上面的代码可以看出,params的生成需要d, g,i这3个参数,前2个是函数传进来的,最后一个是随机生成的。而encSecKey的生成则需要e, f,i这3个参数,前2个是函数传进来的,最后一个和前面相同。
所以理论上我们知道了d,e,f,g这4个参数就可以构造请求了,我们在d函数加断点,继续点击下一页,可以在断点处调试,看到传入的参数:

试了几次后我们发现,无论是同一会话的新请求,还是新会话中的请求,e,f,g的值都是不变的,所以可以初步断定这3个值是固定的,唯一有改变的就是d的值,所以我们只需要在请求时构造好就行了。

参数i的生成

只需要简单的生成16位随机字符串即可

import random
from string import ascii_letters, digits
_charset = ascii_letters + digits
def rand_char(num=16):return ''.join(random.choice(_charset) for _ in range(num))

params的生成

从代码可以看出,2次AES加密中,初始向量都是0102030405060708,加密模式都是CBC加密,不同的是第一次加密中,d作为message,g作为key来加密;第二次加密中,把第一次加密结果作为message,i作为key来加密。我们可以通过Crypto.Cipher中的AES实现,

import base64
from Crypto.Cipher import AESdef aes_encrypt(msg, key, iv='0102030405060708'):def padded(msg):pad = 16 - len(msg) % 16return msg + pad * chr(pad)msg = padded(msg)cryptor = AES.new(key, IV=iv, mode=AES.MODE_CBC)text = cryptor.encrypt(msg)text = base64.b64encode(text)return textdef gen_params(d, g, i):text = aes_encrypt(d, g)text = aes_encrypt(text, i)return text

encSecKey的生成

这个参数通过RSA算法生成,其中i作为message,e,f是加密时用到的参数。
在这里稍微解释一下RSA算法,算法选取2个很大的质数p,q,得到它们的乘积n,然后选取e,d满足e*d = 1 mod (p-1)(q-1),加密时text=(msge)%n,解密时msg=(textd)%n,在这个函数里e就相当于算法里的e,f相当于算法里的n。
还有一点需要注意,encSecKey是一个完全由16进制数组成,但是在加密模块中一般都是返回byte流,然后通过base64编码(长度是原来的4/3),而像这种的应该是把byte流通过16进制表示出来(长度是原来的2倍)。
下面就是用python实现的时候了,我们可以通过Crypto.PublicKey的RSA的construct方法实现。

# 错误版本
import binascii
from Crypto.PublicKey import RSA
cryptor = RSA.construct((0x00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7, 0x10001L))
text = cryptor.encrypt(msg, '')[0]
text = binascii.b2a_hex(text)  # byte流转为16进制

但是这时候问题出现了,上面的代码加密出来的结果和实际不符合,这样看来网易云的RSA加密和标准的有些不同,所以我们要深入到encryptedString这个方法进行调试。

function encryptedString(a, b) {for (var f, g, h, i, j, k, l, c = new Array, d = b.length, e = 0; d > e; )c[e] = b.charCodeAt(e),e++;for (; 0 != c.length % a.chunkSize; )c[e++] = 0;for (f = c.length,g = "",e = 0; f > e; e += a.chunkSize) {for (j = new BigInt,h = 0,i = e; i < e + a.chunkSize; ++h)  // herej.digits[h] = c[i++],j.digits[h] += c[i++] << 8;k = a.barrett.powMod(j, a.e),l = 16 == a.radix ? biToHex(k) : biToString(k, a.radix),g += l + " "}return g.substring(0, g.length - 1)
}

通过代码可以看出,c数组是b字符串转成的数组,然后在for循环中,c数组从左到右是从低位加到高位的,比如123456,1是加在低位,6是加在高位,这和平常有些不一样。
这样看来似乎需要把要加密的消息先翻转一下,然后再进行加密,测试之后发现也确实如此,实现如下:

import binascii
from Crypto.PublicKey import RSAdef rsa_encrypt(msg):cryptor = RSA.construct((0x00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7, 0x10001L))text = cryptor.encrypt(msg[::-1], '')[0]text = binascii.b2a_hex(text)return text

事实上,也可以自己来实现它的加密方式text=(msg^e)%n,只是自己实现的方式效率会比较低。

def rsa_encrypt2(msg):msg = binascii.b2a_hex(msg[::-1])msg = int(msg, 16)text = 1for _ in range(0x10001):text *= msgtext %= 0x00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7return format(text, 'x')


最终实现

import base64
import binascii
import json
import random
import requests
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from string import ascii_letters, digits
_charset = ascii_letters + digitsdef rand_char(num=16):return ''.join(random.choice(_charset) for _ in range(num))def aes_encrypt(msg, key, iv='0102030405060708'):def padded(msg):pad = 16 - len(msg) % 16return msg + pad * chr(pad)msg = padded(msg)cryptor = AES.new(key, IV=iv, mode=AES.MODE_CBC)text = cryptor.encrypt(msg)text = base64.b64encode(text)return textdef gen_params(d, i):text = aes_encrypt(d, '0CoJUm6Qyw8W8jud')text = aes_encrypt(text, i)return textdef rsa_encrypt(msg):cryptor = RSA.construct((0x00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7, 0x10001L))text = cryptor.encrypt(msg[::-1], '')[0]text = binascii.b2a_hex(text)return textdef encrypt(query):query = json.dumps(query)rand_i = rand_char(16)params = gen_params(query, rand_i)enc_sec_key = rsa_encrypt(rand_i)data = {'params': params,'encSecKey': enc_sec_key}return dataif __name__ == '__main__':music_id = '483671599'url = 'http://music.163.com/weapi/v1/resource/comments/R_SO_4_{}?csrf_token='.format(music_id)headers = {'Accept': '*/*','Accept-Encoding': 'gzip, deflate','Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7','Connection': 'keep-alive','Content-Type': 'application/x-www-form-urlencoded','Host': 'music.163.com','Origin': 'http://music.163.com','Referer': 'http://music.163.com/','User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',}query = {'rid': 'R_SO_4_{}'.format(music_id),'offset': '0','total': 'true',  # 第一页时为true,其他页为false'limit': '20','csrf_token': ''}data = encrypt(query)r = requests.post(url, data=data, headers=headers)print(r.content)for item in r.json()['comments']:print(item['content'])

一个套路

通过代码我们可以看见encSecKey是由i决定的,但是这个参数是浏览器这边随机生成的,所以其实是可以写死的,这样一来encSecKey就成了一个固定值,只需要处理params这个参数,当然,会不会因为encSecKey总是不变而被封IP什么的我就不知道了

其它

由于RSA是非对称加密,我们无法通过encSecKey解密出i,没有i也就无法解密params,所以也就只能对每个接口进行断点调试,观察请求的构造,这里提供几个常用接口的参数

歌曲评论
url:http://music.163.com/weapi/v1/resource/comments/R_SO_4_483671599?csrf_token=
d: {“rid”:“R_SO_4_483671599”,“offset”:“20”,“total”:“false”,“limit”:“20”,“csrf_token”:""}

歌曲歌词
url:http://music.163.com/weapi/song/lyric?csrf_token=
d:{“id”:“483671599”,“lv”:-1,“tv”:-1,“csrf_token”:""}

歌单评论
url:http://music.163.com/weapi/v1/resource/comments/A_PL_0_2003824512?csrf_token=
d:{“rid”:“A_PL_0_2003824512”,“offset”:“0”,“total”:“true”,“limit”:“20”,“csrf_token”:""}

搜索
url:http://music.163.com/weapi/cloudsearch/get/web?csrf_token=

搜索单曲:{“hlpretag”:"<span class=“s-fc7”>",“hlposttag”:"",“s”:“爱”,“type”:“1”,“offset”:“0”,“total”:“true”,“limit”:“30”,“csrf_token”:""}

搜索歌手:{“hlpretag”:"<span class=“s-fc7”>",“hlposttag”:"",“s”:“爱”,“type”:“100”,“offset”:“0”,“total”:“true”,“limit”:“90”,“csrf_token”:""}

搜索专辑:{“hlpretag”:"<span class=“s-fc7”>",“hlposttag”:"",“s”:“爱”,“type”:“10”,“offset”:“0”,“total”:“true”,“limit”:“75”,“csrf_token”:""}

搜索MV:{“hlpretag”:"<span class=“s-fc7”>",“hlposttag”:"",“s”:“爱”,“type”:“1004”,“offset”:“0”,“total”:“true”,“limit”:“20”,“csrf_token”:""}

搜索歌词:{“hlpretag”:"<span class=“s-fc7”>",“hlposttag”:"",“s”:“爱”,“type”:“1006”,“offset”:“0”,“total”:“true”,“limit”:“30”,“csrf_token”:""}

搜索歌单:{“hlpretag”:"<span class=“s-fc7”>",“hlposttag”:"",“s”:“爱”,“type”:“1000”,“offset”:“0”,“total”:“true”,“limit”:“30”,“csrf_token”:""}

搜索主播电台:{“hlpretag”:"<span class=“s-fc7”>",“hlposttag”:"",“s”:“爱”,“type”:“1009”,“offset”:“0”,“total”:“true”,“limit”:“30”,“csrf_token”:""}

搜索用户:{“hlpretag”:"<span class=“s-fc7”>",“hlposttag”:"",“s”:“爱”,“type”:“1002”,“offset”:“0”,“total”:“true”,“limit”:“30”,“csrf_token”:""}

最后,膜拜这位大佬。

python 网易云音乐评论爬取3相关推荐

  1. python爬取网易云音乐_Python 从零开始爬虫(七)——实战:网易云音乐评论爬取(附加密算法)...

    前言 某宝评论区已经成功爬取了,jd的也是差不多的方法,说实话也没什么好玩的,我是看上它们分析简单,又没加密才拿来试手的.如果真的要看些有趣的评论的话,我会选择网易云音乐,里面汇聚了哲学家,小说家,s ...

  2. php 爬取一个人的网易云评论,网易云音乐评论爬取

    # coding=gbk import requests import json c='网易云爬虫实战一' print(c) music_url = 'https://music.163.com/#/ ...

  3. 网易云音乐评论爬取。

    欢迎关注天善智能,我们是专注于商业智能BI,人工智能AI,大数据分析与挖掘领域的垂直社区,学习,问答.求职一站式搞定! 对商业智能BI.大数据分析挖掘.机器学习,python,R等数据领域感兴趣的同学 ...

  4. 网易云音乐评论爬取、情感分析一体化

    开局一张图 网易云诞生了很多励志鸡汤,那么多的伤感流行句式,那么多微甜情话,今天我们就看他个天翻地覆,话不多说直接上个干货. 导入包.相关库 import requests import math i ...

  5. python爬取网易云音乐问题陈述_python 网易云音乐 评论爬取问题

    除了使用phantomjs,selenium之外,怎么爬取多页评论,这两个都太慢了.例如http://music.163.com/#/song?i... 的 评论. webapi都是http://mu ...

  6. 利用Python网络爬虫实现对网易云音乐歌词爬取

    今天小编给大家分享网易云音乐歌词爬取方法. 本文的总体思路如下: 找到正确的URL,获取源码: 利用bs4解析源码,获取歌曲名和歌曲ID: 调用网易云歌曲API,获取歌词: 将歌词写入文件,并存入本地 ...

  7. python3爬虫进阶之自动登录网易云音乐并爬取指定歌曲评论

    ** python3爬虫进阶之自动登录网易云音乐并爬取指定歌曲评论 ** 一.访问网易云首页,找到所需元素的位置 用浏览器打开网易云首页https://music.163.com/之后,发现我们要找的 ...

  8. java爬取网易云歌单_[原创]基于Java网易云音乐评论抓取~【悠着点玩啊~】

    本帖最后由 wushaominkk 于 2018-3-20 10:40 编辑 一般我们爬虫都是采用Python,自己闲的无聊就有Java编写一个简单的网易云音乐评论的抓取,这个仅仅是我开发的一个小小调 ...

  9. python爬虫----网易云音乐歌曲爬取并存入Excel

    因为数据要存入Excel中,所以首要目标是找个办法将数据能够存入excel中 经过在网上一番搜索后,发现用python里的xlwt模块可以比较容易的解决 一.准备工作 1.安装xlwt模块: 可以看h ...

最新文章

  1. WindowsPhone7真机部署和调试程序
  2. maven jetty 插件 允许修改 js
  3. 安装SCOM Reporting Server
  4. MySQL IFNull 详解
  5. LeetCode之Merge Two Sorted Lists
  6. 前端学习(1422):ajax获取服务器端的响应
  7. 多个for语句嵌套执行顺序_阿里真实面试题解析之实现多个线程顺序执行的几种方式...
  8. python中什么具有去重功能_python中去重的方法
  9. 网站性能测试工具 webbench 的安装和使用-linux
  10. 3步接入顺丰快递云打印电子面单接口API
  11. 等差素数列 蓝桥杯 python
  12. LaTex编辑器编辑公式
  13. 九峰影业创始人_《勇士之城》林永健扮演棠德县县长魏九峰
  14. java8 Stream详解
  15. vant 验证手机号_Vue 正则表达式验证邮箱和手机号码
  16. 有效年利率和年化百分比利率
  17. 泰山杯练习平台部分题目wp
  18. 实例:用C#.NET手把手教你做微信公众号开发(13)--事件消息处理之取消关注
  19. Android开发技术周报 Issue#27
  20. 这届年轻人有多爱养生?

热门文章

  1. 怎么在计算机里找到CF里保存的视频,cf录像保存在哪?cf怎么样录像保存方法
  2. python绘制动态Julia集,超炫酷
  3. 2021年中国机动车、汽车和新能源汽车保有量及驾驶人和驾驶证业务办理情况分析「图」
  4. 深入理解文字高度和行高的设置
  5. hive以半小时为维度进行统计的需求
  6. smart 完成安装之前向导中断
  7. VUE:全局引入.lees文件的CSS变量
  8. 计算机属于什么学1001计算机属于什么学,怎样学电脑(初学电脑先学什么)
  9. 多个域名泛域名证书和多域名证书
  10. 微信小程序ios端唤醒不了拨打电话或者部分电话拨打不了解决方案