- # -*- coding: utf-8 -*-
- '''
- 16 位随机字符的字符串
- 参数一
- 获取歌曲下载地址 "{"ids":"[1361348080]","level":"standard","encodeType":"aac","csrf_token":""}" 获取歌曲评论信息"{"rid":"R_SO_4_1361348080","offset":"0","total":"true","limit":"20","csrf_token":""}"
- 第二三四为参数是固定的
- "010001"
- "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
- "0CoJUm6Qyw8W8jud"
- encSecKey 通过方法 c, 传入参数 参数一为 16 为随机字符串, 参数二, 三为上面的参数二三
- encText 进行了两次加密 通过方法 b 第一次是通过上面从参数一, 参数四拿到返回值 第二次是返回值和 16 随机字符串加密
- # 关于我遇到的问题
- 1, 首先就是参数一的获取,
- 参数一很容易看得出来就是 json 格式的字典, 通过 json.dumps(dict)就能得到, 对, 当时我也是这样做的, 因为分析网易云进行 js 加密的代码,
- 它也是将对象 (也就是 python 的字典, 类似) 进行 Json.stringify(obj). 所以我就跟着转为 json 格式的数据.
- 代码书写完毕之后, 就进行测试阶段了, 启动脚本, 他给我返回的是 json 格式的字符串, 400 的错误, 提示信息为参数错误, 最终我排除了请求头是否
- 不合法等原因, 那就是我进行加密的时候, 得出的加密结果不对.
- 为了检测是否我加密错误, 我继续在谷歌浏览器的开发者工具调试 js 代码, 因为进行加密的时候有一串 16 位的随机字符串, 我先在浏览器中获取到这个随机字符串
- 然后在我写的 python 代码中, 将那个随机的字符串固定为浏览器获取到的 16 位随机字符串, 并且执行, 比较二者参数哪里对不上, encSecKey 的值是能对上的,
- encText(也就是 formdata 中的 params)是对不上的, 所以我获取 encText 的加密内容出错了. 当时我完全没有考虑到是参数一的错误, 因为我认为参数一是对的, 就是
- json 格式的数据呀, 我认为我加密的逻辑写错了. 我把网易云加密的那段 js 代码, copy 一份到本地的 html 文件中执行, 参数也是一致, 得到的结果也是和浏览器加密后
- 的数据是一致的, 我把加密后的数据直接用在 python 代码中, 执行, 数据成功返回了, 这是我更加肯定是我加密代码写错了,
- 经过一段的测试, 我在本地的 html 文件中, 把参数一的值写成一个很简单的字符串 "aaaa", 我也把 python 代码中的参数一也改为一样. 分别执行, 卧槽, 加密后参数
- 完全一样, 找出了原因, 竟然是我认为不会出错的参数一的原因. 找到原因了, 我就去看看参数一 print 出来到底是啥, js 中参数一 console.log 几次, 它的值是不变的
- 我再看 python 中, 测试几次, 终于知道了原因, 我竟然忽略了字典是无序了,
- dic = {"name":"zhuyu","age":22}
- print(json.dumps(dic))
- # 多执行几次, 下面是输出结果, 你会发现:
- {"name": "zhuyu", "age": 22}
- {"age": 22, "name": "zhuyu"}
- 解决问题一: 字典是无序的, 执行 json.dumps 得到的数据不是固定的, 所以必须要弄一个固定的 json 格式数据, json 数据它也是一个字符串, 我自己弄一个和 js 中的
- json 数据一样的字符串就好了
- 也就是这个>>>: self.arg1 = '{"ids":"[%s]","level":"standard","encodeType":"aac","csrf_token":""}' % songId
- 2, 对加密方法不清楚
- 对这一块确实不擅长, 你看到 js 中的加密代码, 却不知道它传递的参数, 到底有啥用, 是用来做什么的, 你先要懂 js 代码加密的逻辑, 你猜能写 python 代码来实现一样
- 的加密逻辑.
- 解决问题二: 这个只能多百度, Google 了, 了解到加密方法, 传递的参数是什么形式, 参数作用是啥, 返回值又是什么
- 3, 了解网易云 js 加密的流程
- 只有知道流程了, 只能写 python 加密的流程, 这个需要你会 chrome 的开发者工具的使用, 对 js 进行调试, 知道重点的变量代表什么
- 解决问题三: 刚开始确实不会, 需要查看一些博客, 知道每个按钮对应的功能是什么. 还有就是要多看, 多看, 多看, 指的是调试 js 代码
- 4, 知道 js 加密代码的位置
- 我的方法: 先看请求的所携带的参数, 还有就是 url, 请求方法等等... 然后就是 ctrl+shift+f 搜索你认为的关键字, 然后就是慢慢找吧
- 如果请求那里找不到, 也搜索不到加密的 js 位置, 试试搜索 encrypt, 还不行只能百度, Google 了, 看看别人的
- '''
- from Crypto.Cipher import AES
- import base64
- import random
- import codecs
- import requests
- from fake_useragent import UserAgent
- import time
- from multiprocessing import Process
- class DownLoadWYY:
- ua = UserAgent()
- def __init__(self):
- self.arg2 = "010001"
- self.arg3 = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
- self.arg4 = "0CoJUm6Qyw8W8jud"
- self.session = requests.Session()
- self.session.headers = {
- "Referer": "https://music.163.com/",
- "User-Agent": self.ua.random
- }
- self.__get_random_str()
- # self.__init_session()
- def __init_session(self):
- '''拿到后面需要的 cookies'''
- resopnse = self.session.get("https://music.163.com/#/discover/playlist", headers=self.session.headers)
- print(resopnse.headers)
- def __AES_encrypt(self, text, key):
- '''
- 获取到加密后的数据
- :param text: 首先 CBC 加密方法, text 必须位 16 位数据
- :param key: 加密的 key
- :return: 加密后的字符串
- ''' iv ="0102030405060708"
- pad = 16 - len(text) % 16
- if isinstance(text, str):
- text = text + pad * chr(pad)
- else:
- text = text.deocde("utf-8") + pad * chr(pad)
- aes = AES.new(key=bytes(key, encoding="utf-8"), mode=2, iv=bytes(iv, encoding="utf-8"))
- res = aes.encrypt(bytes(text, encoding="utf-8"))
- res = base64.b64encode(res).decode("utf-8")
- return res
- def __get_encText(self):
- encText = self.__AES_encrypt(self.arg1, self.arg4)
- encText = self.__AES_encrypt(encText, self.random_16_str)
- return encText
- def __get_encSecKey(self):
- '''通过查看 js 代码, 获取 encSecKey'''
- text = self.random_16_str[::-1]
- rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(self.arg2, 16) % int(self.arg3, 16)
- return format(rs, 'x').zfill(256)
- def __get_random_str(self):
- '''这是 16 位的随机字符串'''
- str_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- random_str = ""
- for i in range(16):
- index = random.randint(0, len(str_set) - 1)
- random_str += str_set[index]
- self.random_16_str = random_str
- def __getFormData(self):
- '''获取到提交的数据'''
- data = {"params": self.__get_encText(), "encSecKey": self.__get_encSecKey()}
- return data
- def downloadSong(self, songId):
- '''获取到歌曲的下载的地址就好了. 如果想要下载可以单独再写一个方法去下载音乐'''
- print("开始爬取歌曲 mp3 地址....")
- self.arg1 = '{"ids":"[%s]","level":"standard","encodeType":"aac","csrf_token":""}' % songId
- API = "https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token="
- headers = self.session.headers.copy()
- formdata = self.__getFormData()
- response = self.session.post(url=API, data=formdata, headers=headers)
- print("歌曲的下载地址为>>:", response.JSON()["data"][0]["url"])
- def song_comment(self, songId):
- '''获取到歌曲评论信息, 我只是将结果 print 出来, 如果保存的话, 可以单独写一个保存的方法'''
- print("开始爬取歌曲评论信息....")
- # self.arg1 的格式为:"{"rid":"R_SO_4_歌曲 id","offset":"0","total":"true","limit":"20","csrf_token":""}"
- # 第一页为 0, 第二页为 20, 第三页为 40 第四页为 60 第五页为 80
- offset = 0
- n = 1
- API = "https://music.163.com/weapi/v1/resource/comments/R_SO_4_{}?csrf_token=".format(songId)
- headers = self.session.headers.copy()
- while True:
- self.arg1 = '{"rid":"R_SO_4_%s","offset":"%s","total":"true","limit":"20","csrf_token":""}' % (
- songId, offset)
- formdata = self.__getFormData()
- response = self.session.post(url=API, headers=headers, data=formdata)
- # print("*"*100)
- # print("第 {} 页评论".format(n))
- comment_list = response.JSON().get("comments")
- for dic in comment_list:
- try:
- print("用户: {}".format(dic["user"]["nickname"]))
- print("评论内容: {}".format(dic.get("content")))
- print()
- except UnicodeEncodeError:
- pass
- offset += 20
- n += 1
- time.sleep(1)
- if __name__ == '__main__':
- song = DownLoadWYY()
- comment = DownLoadWYY()
- p1 = Process(target=song.downloadSong, args=(1361348080,))
- p2 = Process(target=comment.song_comment, args=(1361348080,))
- p1.start()
- p2.start()
- p1.join()
- p2.join()
- print("爬取完毕...")
来源: http://www.bubuko.com/infodetail-3037725.html