Python爬虫逆向分析某云音乐加密参数的实例分析


Posted in Python onDecember 04, 2020

本文转自:https://blog.csdn.net/qq_42730750/article/details/108415551

前言

Python爬虫逆向分析某云音乐加密参数的实例分析

  各大音乐平台是从何时开始收费的这个问题没有追溯过,印象中酷狗在16年就已经开始收费了,貌似当时的收费标准是付费音乐下载一首2元,会员一月8元,可以下载300首。虽然下载收费,但是还可以正常听歌。陆陆续续,各平台不仅收费,而且还更在乎版权问题,因为缺少版权,酷狗上以前收藏的音乐也不能听了,更过分的是,有些歌非VIP会员只能试听60秒(•́へ•́?)。

Python爬虫逆向分析某云音乐加密参数的实例分析

  版权问题重视起来当然是好事,但只是闲暇时来听听音乐放松一下自己的我来说,不会因为想听音乐而开通各个音乐平台的VIP的┗( ?, ? )┛,所以渐渐就有了些想法:能不能将这些音乐整合起来,比如我去酷狗音乐听某一首歌,发现没有版权或只能试听,能不能自动去网易云音乐搜索下载到本地(干脆直接下载到酷狗对应的文件夹里),如果还没有就去QQ音乐、虾米音乐、百度音乐等等。

Python爬虫逆向分析某云音乐加密参数的实例分析

 本篇就是在这样的背景下,通过对网易云音乐进行逆向分析,进而用代码的方式来*********(此处自己体会哦( ̄︶ ̄)?)。
  目标:通过输入歌名或者歌手名,列出相应的音乐信息,然后通过选择某一项,将对应的音乐下载到本地指定目录。
  工具:Google Chrome、PyCharm
  这里以我最喜欢的歌手本兮为例,通过搜索网易云的Web端和PC端发现,Web端不支持下载,PC端需要RMB才能下载(不愧是我兮的歌(✪ω✪)),咳咳咳,OK,Fine,意料之中。

Python爬虫逆向分析某云音乐加密参数的实例分析

Python爬虫逆向分析某云音乐加密参数的实例分析

1. 请求分析

  如果想要下载一首歌,我们首先要获取到这首歌所对应的 u r l url url。随机选择一首歌进行播放,打开Chrome的开发者工具,刷新看一下对应的请求,找到我们想要的歌曲文件的 u r l url url,就是下面这个:

Python爬虫逆向分析某云音乐加密参数的实例分析

  然后找到该请求对应的 u r l url url,分析一下该请求:

Python爬虫逆向分析某云音乐加密参数的实例分析

  可知,获取数据的 u r l url url 为https://music.xxx.com/weapi/song/enhance/player/url/v1?csrf_token=,请求方式为POST。继续往下滑,找到提交的数据:

Python爬虫逆向分析某云音乐加密参数的实例分析

  POST提交了两个参数paramsencSecKey,很明显这两个参数都经过了加密处理,而且经过不断提交刷新发现,这两个参数值会变,可以猜测到加密时应该是有随机操作,但其长度始终不变,即参数params的长度为152,参数encSecKey的长度为256
  需要的 u r l url url 及请求所需要的参数已经找到,下面需要确定一下两个参数是如何加密的。

2. 参数分析

  通过Ctrl + Shift + F全局搜索参数encSecKey定位到了两个文件,然后在core_7a734ef25ee51b62727eb55c7f6eb1e8.js这个文件里通过Ctrl + F定位到了接口函数:

Python爬虫逆向分析某云音乐加密参数的实例分析

  摘取这部分函数分析一下:

var bVZ8R = window.asrsea(JSON.stringify(i0x), bqN0x(["流泪", "强"]), bqN0x(Wx5C.md), bqN0x(["爱心", "女孩", "惊恐", "大笑"]));
e0x.data = j0x.cs1x({
 params: bVZ8R.encText,
 encSecKey: bVZ8R.encSecKey
})

  函数window.asrsea()应该就是加密函数,传入四个参数,将加密后的结果赋值给变量bVZ8R,返回的结果有两个属性,即encTextencSecKey,也就是我们想要的参数paramsencSecKey。在这里设置一个断点,看一下这几个参数:

Python爬虫逆向分析某云音乐加密参数的实例分析

  通过最右边的变量查看区Watch可以看到变量bVZ8R的值就是我们需要的参数的值,这证实了函数window.asrsea()就是加密函数,然后我们在控制台Console打印一下这几个变量:

>JSON.stringify(i0x)
<"{"csrf_token":""}"
>bqN0x(["流泪", "强"])
<"010001"
>bqN0x(Wx5C.md)
<"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
>bqN0x(["爱心", "女孩", "惊恐", "大笑"])
<"0CoJUm6Qyw8W8jud"

  即加密函数window.asrsea()所需的四个参数值已经确定,分别是字符串"{"csrf_token":""}""010001""00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7""0CoJUm6Qyw8W8jud",如果没有猜错的话第三个参数是十六进制的形式,其实也就是如此。通过几次刷新,这几个值不变。

3. 加密分析

  百度搜索发现函数window.asrsea()不是JavaScript的原生函数,应该是开发者自己定义的,然后我通过搜索asrsea定位到了该函数的初始定义位置:

Python爬虫逆向分析某云音乐加密参数的实例分析

  函数window.asrsea()就是函数d,它就是我们要找的加密函数,它接收的d、e、f、g四个参数对应的就是window.asrsea()函数的四个参数,即

d = "{\"csrf_token\":\"\"}"
	e = "010001"
	f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
	g = "0CoJUm6Qyw8W8jud"

  或许已经发现了吧,这里面的函数名、变量名及参数都是一个字母,而且它们有的还相同,没错,这是一种很常见的反爬虫手段------JS代码混淆。
  摘取这部分加密函数分析一下:

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
 }

  函数a的作用是从字符串"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"中随机生成长度为a的字符串。

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()
 }

  函数b的作用是对数据a进行AES加密,模式为CBC,最后通过toString()方法将结果转成字符串。

function c(a, b, c) {
  var d, e;
  return setMaxDigits(131),
  d = new RSAKeyPair(b,"",c),
  e = encryptedString(d, a)
 }

  函数c的作用是对数据a进行RSA加密,返回的结果是十六进制形式的字符串。

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进行加密,得到两个加密的结果encTextencSecKey,加密流程是通过函数a随机产生一个长度为16的字符串,然后通过函数b进行第一次AES加密,然后再通过函数b对第一次的加密结果进行一次AES加密,得到结果encText,即对应我们的params,最后通过函数c进行一次RSA加密,得到结果encSecKey

4. 模拟加密

  这里使用一个非常强大的加密算法库-----PyCryptodome,具体使用方法请参考官方文档。

  这里定义了一个EncryptText类,专门用来模拟JavaScript的加密过程:

class EncryptText:
 def __init__(self):
  self.character = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
  self.iv = '0102030405060708'
  self.public_key = '010001'
  self.modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b' \
      '5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417' \
      '629ec4ee341f56135fccf695280104e0312ecbda92557c93' \
      '870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b' \
      '424d813cfe4875d3e82047b97ddef52741d546b8e289dc69' \
      '35b3ece0462db0a22b8e7'
  self.nonce = '0CoJUm6Qyw8W8jud'

  在函数d中打上断点,来分析看一下abc三个函数返回的结果,方便比对我们模拟的结果:

Python爬虫逆向分析某云音乐加密参数的实例分析

  程序执行到函数a处,在最右边变量作用域区Scope可以看到各个变量的值及函数a返回的的结果i: "mEXyqHtNW5dxT5IK"
  这里先模拟函数a来随机产生长度为16的字符串,首先使用的是官方提供的API:Crypto.Random.get_random_bytes(N),返回长度为N的随机字节串。

def create16RandomBytes(self):
  """
  # 产生16位随机字符, 对应函数a
  :return:
  """
  generated_string = get_random_bytes(16)
  return generated_string

  我们需要将该字节串通过decode()方法转换成字符串,但是随机产生的字节串是这样的:b'\xe0\xda\xf9\x8fd\xb4M\xaa\xa7\x1fW\xaay\x12\x90@',在转换字符串时就会产生UnicodeDecodeError,所以这里就自己写了一个方法:

def create16RandomBytes(self):
  """
  # 产生16位随机字符, 对应函数a
  :return:
  """
  generate_string = random.sample(self.character, 16)
  generated_string = ''.join(generate_string)
  return generated_string

  该方法产生的结果就是16位随机的字符串:

Python爬虫逆向分析某云音乐加密参数的实例分析  

程序执行到函数b处,传入的参数dg的值我们已经知道,看一下加密后的结果:

Python爬虫逆向分析某云音乐加密参数的实例分析

  加密后的结果为encText: "eHhjXckqrtZkqcwCalCMx0QuU6Lj9L7Wxouw1iMCnB4=",下面来用官方的API来模拟一下:

def AESEncrypt(self, clear_text, key):
  """
  AES加密, 对应函数b
  :param clear_text: 需要加密的数据
  :return:
  """
  # 数据填充
  clear_text = pad(data_to_pad=clear_text.encode(), block_size=AES.block_size)
  key = key.encode()
  iv = self.iv.encode()
  aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
  cipher_text = aes.encrypt(plaintext=clear_text)
  # 字节串转为字符串
  cipher_texts = base64.b64encode(cipher_text).decode()
  return cipher_texts

  我们将需要加密的数据"{"csrf_token":""}"传入到该函数中,看一下模拟的结果:

Python爬虫逆向分析某云音乐加密参数的实例分析

  很nice,结果一模一样,然后再进行一次AES加密,因为第二次加密用到了函数a产生的16位随机字符,为了结果一致,这里也使用相同的随机字符进行模拟。先看一下原始的结果:

Python爬虫逆向分析某云音乐加密参数的实例分析

  第二次AES加密产生的结果为encText: "JWuA4mdNsTdrLdDkD9UWs8ShPCZNK0n4BLpdQEDSAaD/kFKKih8XQp8W/mICYPlN",然后对比一下自己模拟的结果:

Python爬虫逆向分析某云音乐加密参数的实例分析

  哈哈哈哈(⁎˃ᴗ˂⁎)也是OK的,结果一样。

  AES具体的加密原理这里不做过多的介绍,感兴趣的话可以参考相关的书籍或自行百度,这里只介绍一些基本概念。
  高级加密标准 ( A d v a n c e d (Advanced (Advanced E n c r y p t i o n Encryption Encryption S t a n d a r d , A E S ) Standard,AES) Standard,AES)是一种分组密码算法,又称 R i j n d a e l Rijndael Rijndael算法,是对称密钥加密中最流行的算法之一。AES的分组长度固定为128位,密钥长度则可以是128、192或256位。
  密码分组链模式,即CBC,是分组密码工作模式之一,它需要一个初始向量 ( I n i t i a l i z a t i o n (Initialization (Initialization V e c t o r , I V ) Vector,IV) Vector,IV)组进行异或运算,而且CBC模式要求数据长度必须是密码分组长度的整数倍。因此数据长度不够的话需要进行填充。

  最后就是RSA加密了,看一下函数c返回的结果:

Python爬虫逆向分析某云音乐加密参数的实例分析

  很长的一串,长度为256:encSecKey: "d58e873a2e908c0599b497456f1842d1734e1d17e834a221ed84d828b06b149d0bac2ddd449e38b7e5e9ce53dcb1aa43a241742a2b273434b67825743fbca6371aa143a4460477704ba3fd33b517619386daf8da4c7fe8d67a604ea0e461aedee5ae2698400a6c7340ab250c97622aa221d871b7352d81ea09262978facf5480"
  下面来模拟一下,我首先使用的是官方的API:Crypto.PublicKey.RSA产生密钥对,然后使用Crypto.Cipher.PKCS1_OAEP进行加密,加密后的数据长度是256位,通过它进行请求 u r l url url 时请求状态码是200,但请求的内容为空,由于RSA每次加密得到数据都不一样,所以目前我还没有好的想法来确定问题出在哪里。

def RSAEncrypt(self, session_key):
  """
  RSA加密的结果每次都不一样
  :param session_key:
  :return:
  """
  # n和e构成公钥
  # (n, e)
  # key = RSA.RsaKey(n=int(self.modulus, 16), e=int(self.public_key, 16))
  key = RSA.construct(rsa_components=(int(self.modulus, 16), int(self.public_key, 16)))
  public_key = key.publickey()
  rsa = PKCS1_OAEP.new(key=public_key)
  cipher_text = rsa.encrypt(message=session_key).hex()
  return cipher_text

Python爬虫逆向分析某云音乐加密参数的实例分析

  根据RSA加密原理,我就自己写了一个函数来模拟RSA加密的过程:

def RSAEncrypt(self, i, e, n):
  """
  RSA加密, 对应函数c
  :param i:
  :return:
  """
  # num = pow(x, y) % z
  # 加密C=M^e mod n
  num = pow(int(i[::-1].encode().hex(), 16), int(e, 16), int(n, 16))
  result = format(num, 'x')
  return result

Python爬虫逆向分析某云音乐加密参数的实例分析

  没错,也是一模一样的(^_^)Y Ya!!

  RSA是由美国麻省理工学院的三名密码学者 R i v e s t Rivest Rivest、 S h a m i r Shamir Shamir和 A d l e m a n Adleman Adleman提出的一种基于大合数因式分解困难性的公开密钥密码,简称RSA密码。RSA算法基于一个十分简单的数论事实,即将两个大素数相乘很容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。由于这次只用到了加密过程,所以RSA的解密过程不做过多的涉及。
  加密运算: C = M e C=M^e C=Me m o d mod mod n n n,其中 C C C是加密后的数据, M M M是被加密的数据, e e e是随机的一个整数, 1 < e < ϕ ( n ) 1<e<\phi (n) 1<e<ϕ(n), ϕ ( n ) \phi (n) ϕ(n)是一个数论函数,称为欧拉函数,表示在比 n n n小的正整数中与 n n n互素的数的个数, n n n是两个大素数的乘积, e e e和 n n n是公开的,它们构成了用户的公钥。

  整个加密流程我们模拟完了,结果也是正确的,但是,这里还存在一个问题,我们模拟出来的encText,也就是参数params长度不够。这里可以确定的是加密算法是没有错误的,传入的参数中d、e、f、g后面三个值是固定的,所以问题就基本锁定了:参数d的值不对。
  我继续debug,然后发现了一些端倪:函数d又接收到了新的参数d,它的值是这样的:

Python爬虫逆向分析某云音乐加密参数的实例分析

  将它进行两次AES加密后encText的数据长度达到了128,说明这个还不是正确的,而且Network面板并没有出现我们想要的v1?csrf_token=,然后继续debug,最终得到了参数d真正的值:d: "{"ids":"[35440198]","level":"standard","encodeType":"aac","csrf_token":""}",最后我们看一下最终的结果:

Python爬虫逆向分析某云音乐加密参数的实例分析

Python爬虫逆向分析某云音乐加密参数的实例分析

  使用模拟加密获取到的两个参数再次发起请求,便可以得到我们想要的数据:

Python爬虫逆向分析某云音乐加密参数的实例分析  

歌曲的文件对应的 u r l url url 我们已经找到,根据结果可知,它是一个字符串,准确来说是个json格式的,而且里面只有一条数据是我们需要的,所以直接提取:

Python爬虫逆向分析某云音乐加密参数的实例分析

然后再去用代码请求该 u r l url url,将请求到的内容以二进制形式进行保存,文件名后缀为.mp3

5. 获取ID

  上面实现的只是一首歌的下载,如果要实现我们的要求,还需要再修改一些参数d,有两个参数需要注意,即idslevel,一个是歌曲的id,另一个应该是歌曲的质量(有标准、无损等,我猜的),这里只关注一个,那就是歌曲的id。很容易猜到,一首歌对应一个id,我们选择哪首歌,就会得到哪首歌的id,那在哪选择呢???毫无疑问,肯定是在搜索结果中选择的。
  正常情况下,我们输入歌手名,会搜索出来许多歌手的音乐,就像下面这样:

Python爬虫逆向分析某云音乐加密参数的实例分析

  我们通过代码直接访问https://music.xxx.com/#/search/m/?s=本兮&type=1并不会得到我们想要的信息,该 u r l url url 请求得到的是网站的源代码,不包含数据在里面,很明显是通过 J a v a S c r i p t JavaScript JavaScript 动态获得的,所以我们要找到请求数据的 u r l url url。打开Chrome的开发者工具,刷新看一下对应的请求,找到我们想要的数据,就是下面这个:

Python爬虫逆向分析某云音乐加密参数的实例分析

  然后找到对应的 u r l url url,分析一下该请求:

Python爬虫逆向分析某云音乐加密参数的实例分析

  可知,获取数据的 u r l url url 为https://music.xxx.com/weapi/cloudsearch/get/web?csrf_token=,请求方式为依旧是POST。继续往下滑,找到提交的数据:

Python爬虫逆向分析某云音乐加密参数的实例分析

  POST提交了两个参数paramsencSecKey,和我们获取歌曲 u r l url url 时一样,但参数params的长度变为了280,参数encSecKey的长度依旧不变,为256。由此可以确定,又是参数d发生了变化。经过几次debug,最终确定了参数d的值:d = "{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"本兮","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}"

Python爬虫逆向分析某云音乐加密参数的实例分析

  结果也是一样的:

Python爬虫逆向分析某云音乐加密参数的实例分析

  使用模拟加密获取到的两个参数再次发起请求,发现得到的结果是空的,然后改了一下,将字典转为json格式,AES二次加密后参数params长度变为了300,然而却得到了数据。和我们在开发者模式下看到的结果一样,里面包含歌曲名、歌曲的id以及歌手名等信息。

Python爬虫逆向分析某云音乐加密参数的实例分析

 从Network更容易看到json里面的数据结构:

Python爬虫逆向分析某云音乐加密参数的实例分析

  提取到的结果如下,分别是歌手名、歌曲名、歌曲id、时长、专辑名、专辑图片的url:

Python爬虫逆向分析某云音乐加密参数的实例分析  

这里简单分析一下参数d,关键字s表示你要搜索的内容,关键字type表示搜索的类型(见下面的表格),如果需要下载其他歌手的歌曲,只需要将参数d中的关键字s的值改一下即可,为了方便,可以用input()方法传递这个值。

type 含义
1 单曲
100 歌手
10 专辑
1014 视频
1006 歌词
1000 歌单
1009 主播电台
1002 用户

6. 代码框架

# -*- coding: utf-8 -*-
# @Time : 2020/9/2 11:23
# @Author : XiaYouRan
# @Email : youran.xia@foxmail.com
# @File : wangyiyun_music2.py
# @Software: PyCharm

import requests
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Util.Padding import pad
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
import random
import base64
import json
import os


class EncryptText:
 def __init__(self):
  self.character = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
  self.iv = '0102030405060708'
  self.public_key = '010001'
  self.modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b' \
      '5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417' \
      '629ec4ee341f56135fccf695280104e0312ecbda92557c93' \
      '870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b' \
      '424d813cfe4875d3e82047b97ddef52741d546b8e289dc69' \
      '35b3ece0462db0a22b8e7'
  self.nonce = '0CoJUm6Qyw8W8jud'

 def create16RandomBytes(self):


 def AESEncrypt(self, clear_text, key):


 def RSAEncrypt(self, i, e, n):


 def resultEncrypt(self, input_text):
  """
  对应函数d
  :param input_text:
  :return:
  """
  i = self.create16RandomBytes()
  encText = self.AESEncrypt(input_text, self.nonce)
  encText = self.AESEncrypt(encText, i)
  encSecKey = self.RSAEncrypt(i, self.public_key, self.modulus)
  from_data = {
   'params': encText,
   'encSecKey': encSecKey
  }
  return from_data


class WangYiYunMusic(object):
 def __init__(self):
  self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
          'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}

 def get_html(self, url, method='GET', from_data=None):
  try:
   if method == 'GET':
    response = requests.get(url, headers=self.headers)
   else:
    response = requests.post(url, from_data, headers=self.headers)
   response.raise_for_status()
   response.encoding = 'utf-8'
   return response.text
  except Exception as err:
   print(err)
   return '请求异常'

 def parse_text(self, text):
  ids_list = json.loads(text)['result']['songs']
  count = 0
  info_list = []
  print('{:*^80}'.format('搜索结果如下'))
  print('{0:{5}<5}{1:{5}<20}{2:{5}<10}{3:{5}<10}{4:{5}<20}'.format('序号', '歌名', '歌手', '时长(s)', '专辑', chr(12288)))
  print('{:-^84}'.format('-'))
  for id_info in ids_list:
   song_name = id_info['name']
   id = id_info['id']
   time = id_info['dt'] // 1000
   album_name = id_info['al']['name']
   picture_url = id_info['al']['picUrl']
   singer = id_info['ar'][0]['name']
   info_list.append([id, song_name, singer])
   print('{0:{5}<5}{1:{5}<20}{2:{5}<10}{3:{5}<10}{4:{5}<20}'.format(count, song_name, singer, time, album_name, chr(12288)))
   count += 1
   if count == 8:
    # 为了测试方便, 这里只显示了9条数据
    break
  print('{:*^80}'.format('*'))
  return info_list

 def save_file(self, song_text, download_info):
  filepath = './download'
  if not os.path.exists(filepath):
   os.mkdir(filepath)
  filename = download_info[1] + '-' + download_info[2]
  music_url = json.loads(song_text)['data'][0]['url']
  response = requests.get(music_url, headers=self.headers)
  with open(os.path.join(filepath, filename) + '.mp3', 'wb') as f:
   f.write(response.content)
   print("下载完毕!")


if __name__ == '__main__':
 id_url = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token='
 song_url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token='

 id_d = {
  "hlpretag": "<span class=\"s-fc7\">",
  "hlposttag": "</span>",
  "s": input("请输入歌名或歌手: "),
  "type": "1",
  "offset": "0",
  "total": "true",
  "limit": "30",
  "csrf_token": ""
 }

 encrypt = EncryptText()
 id_from_data = encrypt.resultEncrypt(str(id_d))

 wyy = WangYiYunMusic()
 id_text = wyy.get_html(id_url, method='POST', from_data=id_from_data)
 info_list = wyy.parse_text(id_text)

 while True:
  input_index = eval(input("请输入要下载歌曲的序号(-1退出): "))
  if input_index == -1:
   break
  download_info = info_list[input_index]
  song_d = {
   "ids": str([download_info[0]]),
   "level": "standard",
   "encodeType": "aac",
   "csrf_token": ""
  }
  song_from_data = encrypt.resultEncrypt(str(song_d))

  song_text = wyy.get_html(song_url, method='POST', from_data=song_from_data)
  wyy.save_file(song_text, download_info)

  测试结果如下,等有时间了再做一个GUI٩(๑>◡<๑)۶ :

Python爬虫逆向分析某云音乐加密参数的实例分析
Python爬虫逆向分析某云音乐加密参数的实例分析

结束语

  最后,加一个彩蛋吧,这个代码不仅可以download,还可以搜集用户的评论、歌曲对应的歌词等信息,只需要改一下参数d和请求的 u r l url url 即可。这里给出这些参数:

功能 参数d dd u r l urlurl
搜索信息 “{“hlpretag”:”",“hlposttag”:"",“s”:"你要搜索的信息",“type”:"1",“offset”:“0”,“total”:“true”,“limit”:“30”,“csrf_token”:""}" https://music.xxx.com/weapi/cloudsearch/get/web?csrf_token=
下载音乐 “{“ids”:”[歌曲id]",“level”:"standard",“encodeType”:“aac”,“csrf_token”:""}" https://music.xxx.com/weapi/song/enhance/player/url/v1?csrf_token=
下载歌词 “{“id”:”歌曲id",“lv”:-1,“tv”:-1,“csrf_token”:""}" https://music.xxx.com/weapi/song/lyric?csrf_token=
搜集用户评论 “{“rid”:“R_SO_4_歌曲id”,“threadId”:“R_SO_4_歌曲id”,“pageNo”:“1”,“pageSize”:“20”,“cursor”:”-1",“offset”:“0”,“orderType”:“1”,“csrf_token”:""}" https://music.xxx.com/weapi/comment/resource/comments/get?csrf_token=

  这些参数并不是一成不变的,如果网站更新了这些参数,那就需要重新做分析了。

开源代码仓库


  如果喜欢的话记得给我的GitHub仓库点个Star哦!?(?∇?*)?

到此这篇关于Python爬虫逆向分析某云音乐加密参数的文章就介绍到这了,更多相关Python爬虫逆向加密参数内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python下的subprocess模块的入门指引
Apr 16 Python
Python使用chardet判断字符编码
May 09 Python
Python中的pass语句使用方法讲解
May 14 Python
Python常用时间操作总结【取得当前时间、时间函数、应用等】
May 11 Python
Python装饰器用法示例小结
Feb 11 Python
从0开始的Python学习014面向对象编程(推荐)
Apr 02 Python
Python中面向对象你应该知道的一下知识
Jul 10 Python
win10系统Anaconda和Pycharm的Tensorflow2.0之CPU和GPU版本安装教程
Dec 03 Python
Python 实现自动获取种子磁力链接方式
Jan 16 Python
Python编程快速上手——Excel到CSV的转换程序案例分析
Feb 28 Python
Python单元测试及unittest框架用法实例解析
Jul 09 Python
python正则表达式的懒惰匹配和贪婪匹配说明
Jul 13 Python
浅谈Selenium 控制浏览器的常用方法
Dec 04 #Python
详解Selenium 元素定位和WebDriver常用方法
Dec 04 #Python
Selenium 安装和简单使用的实现
Dec 04 #Python
python 爬虫之selenium可视化爬虫的实现
Dec 04 #Python
Python实现中英文全文搜索的示例
Dec 04 #Python
一文带你了解Python 四种常见基础爬虫方法介绍
Dec 04 #Python
使用Python通过oBIX协议访问Niagara数据的示例
Dec 04 #Python
You might like
php 分库分表hash算法
2009/11/12 PHP
php自动给网址加上链接的方法
2015/06/02 PHP
php中分页及SqlHelper类用法实例
2017/01/12 PHP
Prototype 学习 工具函数学习($方法)
2009/07/12 Javascript
让人期待的2011年度最佳 jQuery 插件分享
2012/03/16 Javascript
用JavaScript显示浏览器客户端信息的超相近教程
2015/06/18 Javascript
安装使用Mongoose配合Node.js操作MongoDB的基础教程
2016/03/01 Javascript
jQuery学习心得总结(必看篇)
2016/06/10 Javascript
JavaScript动态添加css样式和script标签
2016/07/19 Javascript
js制作可以延时消失的菜单
2017/01/13 Javascript
ES6中Iterator与for..of..遍历用法分析
2017/03/31 Javascript
详解vue.js 开发环境搭建最简单攻略
2017/06/12 Javascript
js 毫秒转天时分秒的实例
2017/11/17 Javascript
vue移动端实现红包雨效果
2020/06/23 Javascript
详解滑动穿透(锁body)终极探索
2019/04/16 Javascript
一次让你了解全部JavaScript的作用域
2019/06/24 Javascript
Vue数据双向绑定底层实现原理
2019/11/22 Javascript
JavaScript枚举选择jquery插件代码实例
2020/11/17 jQuery
[55:45]LGD vs OG 2019国际邀请赛淘汰赛 胜者组 BO3 第三场 8.24
2019/09/10 DOTA
python+Django+apache的配置方法详解
2016/06/01 Python
python 迭代器和iter()函数详解及实例
2017/03/21 Python
使用Python进行QQ批量登录的实例代码
2018/06/11 Python
Pandas Shift函数的基础入门学习笔记
2018/11/16 Python
pymongo中group by的操作方法教程
2019/03/22 Python
python如何解析配置文件并应用到项目中
2019/06/27 Python
python3 实现的对象与json相互转换操作示例
2019/08/17 Python
Python django框架输入汉字,数字,字符生成二维码实现详解
2019/09/24 Python
Python 如何调试程序崩溃错误
2020/08/03 Python
Python脚本打包成可执行文件过程解析
2020/10/20 Python
HTML5的文档结构和新增标签完全解析
2017/04/21 HTML / CSS
css 如何让背景图片拉伸填充避免重复显示
2013/07/11 HTML / CSS
意大利制造的西装、衬衫和针对男士量身定制的服装:Lanieri
2018/04/08 全球购物
The Beach People美国:澳洲海滨奢华品牌
2018/07/05 全球购物
导师对论文的学术评语
2015/01/04 职场文书
阿里云Nginx配置https实现域名访问项目(图文教程)
2021/03/31 Servers
使用HttpSessionListener监听器实战
2022/03/17 Java/Android