Python爬取网易云音乐热评(附源码)
一、获取歌曲信息
爬取热歌榜的200首歌曲的热评
通过分析不难发现这些歌曲并不在网页源代码中,这些歌曲信息都是动态加载的
点击检查,Network,F5刷新一下网页
粗略的预览浏览器返回的信息看出这个toplist?id=3778678中就有我们想要的歌曲信息
请求的URL
歌曲名和歌曲的ID
二、获取热评信息
选择一首歌,点击进入,查看下面的评论信息
同样查看网页源代码,这些信息也不再源代码中
点击检查,Network,F5刷新一下网页
通过查看浏览器返回的信息可以看出我们想要的热评在**get?csrf_token=**中
接下来就是获取热评信息了
三、破解JS加密
当我们向这个网页发送请求后根本得不到热评信息
查看POST请求提交的data信息因该是加密的
因此只要知道params和encSecKey这两个值是如何加密的就能爬取评论信息
点击Initiator查看执行的js程序有哪些
点击第一个是执行的最后一下JS程序,查看到了data的信息,设置断点,F5刷新,调试跟踪data的信息变化
通过调试可以发现下图中的data还没有被加密,通过分析这个i3x中的数据就是要提交的信息
那么这个程序的下一步因该就是data的加密过程,通过分析我们找到了params和encSecKey的值
其中执行的window.asrsea就是params和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
}
window.asrsea = d,
分析可得window.asrsea就是d,d中的h.encText,h.encSecKey就是params和encSecKey的值
d函数中有四个参数分别是d,e,f,g 查看window.asrsea的调用可知参数d就是提交的data,e,f,g是定值
先分析函数c可知函数c是加密encSecKey,c的参数中e,f是定值,i是函数a生成的,分析函数a可知这是一个产生16位随机数的函数,如果i的值是定值则encSecKey的值也一定是定值
通过调试浏览器确定一个i的值之后得到encSecKey的值
#1808492017是歌曲的ID,pageNo是页数
data = {
'cursor': '-1',
'offset': '0',
'orderType': '1',
'pageNo': '1',
'pageSize': '20',
'rid': 'R_SO_4_1808492017',
'threadId': 'R_SO_4_1808492017'
}
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
# 随机值
i = 'vDIsXMJJZqADRVBP'
def get_encSecKey():
encSecKey = "516070c7404b42f34c24ef20b659add657c39e9c52125e9e9f7f5441b4381833a407e5ed302cac5d24beea1c1629b17ccb86e0d9d57f6508db5fb7a6df660089ac57b093d19421d386101676a1c8d1e312e099a3463f81fbe91f28211f9eccccfbfc64148fdd65e2b9f5fcf439a865b95fb656e36f75091957f0a1d39ca8ddd3"
return encSecKey
接下来就是确定encText的值
可以看出encText是执行了两次b函数后得到的,分析b函数可以得出这是一个AES加密,密钥是‘0102030405060708’,加密的数据就是data,加密模式是CBC
以此用Python实现AES的加密过程就可以得到encText的值了,也就是params的值
python 在 Windows下使用AES时要安装的是pycryptodome 模块
pip install pycryptodome
AES加密过程
def get_params(data):
#两次AES加密
first = enconda_params(data,g)
second = enconda_params(first,i)
return second
# 加密params
def enconda_params(data,key):
d = 16 - len(data) % 16
data += chr(d) * d
data = data.encode('utf-8')
aes = AES.new(key=key.encode('utf-8'),IV='0102030405060708'.encode('utf-8'),mode=AES.MODE_CBC)
bs = aes.encrypt(data)
#b64解码
params = b2a_base64(bs).decode('utf-8')
return params
四、保存热评
这里我使用了MySQL数据库保存
Python连接MySQL数据库需要安转pymysql模块
pip install pymysql
建表
CREATE TABLE `music_163` (
`song_id` int DEFAULT NULL,
`song_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`comments` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
连接数据库
#这里注意:charset选择utf8mb4,因为评论中存在表情符号
#数据库建表的字符集也要选择utf8mb4
connect = pymysql.Connect(
host='localhost',
port=3306,
user='root',
passwd='root',
db='wangyi',
charset='utf8mb4'
)
保存信息
五、程序代码
import requests
from Crypto.Cipher import AES
from lxml import etree
from binascii import b2a_base64
import json
import time
import pymysql
from pymysql.converters import escape_string
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36'
}
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
# 随机值
i = 'vDIsXMJJZqADRVBP'
def get_163():
#热歌榜URL
toplist_url = 'https://music.163.com/discover/toplist?id=3778678'
response = requests.get(toplist_url,headers=headers)
html = response.content.decode()
html = etree.HTML(html)
namelist = html.xpath("//div[@id='song-list-pre-cache']/ul[@class='f-hide']/li")
#可选择保存到文件
# f = open('./wangyi_hotcomments.txt',mode='a',encoding='utf-8')
for name in namelist:
song_name = name.xpath('./a/text()')[0]
song_id = name.xpath('./a/@href')[0].split('=')[1]
content = get_hotConmments(song_id)
print(song_name,song_id)
save_mysql(song_id,song_name,content)
# f.writelines(song_id+song_name)
# f.write('\n')
# f.write(str(content))
# f.close()
def get_encSecKey():
encSecKey = "516070c7404b42f34c24ef20b659add657c39e9c52125e9e9f7f5441b4381833a407e5ed302cac5d24beea1c1629b17ccb86e0d9d57f6508db5fb7a6df660089ac57b093d19421d386101676a1c8d1e312e099a3463f81fbe91f28211f9eccccfbfc64148fdd65e2b9f5fcf439a865b95fb656e36f75091957f0a1d39ca8ddd3"
return encSecKey
def get_params(data):
first = enconda_params(data,g)
second = enconda_params(first,i)
return second
# 加密params
def enconda_params(data,key):
d = 16 - len(data) % 16
data += chr(d) * d
data = data.encode('utf-8')
aes = AES.new(key=key.encode('utf-8'),IV='0102030405060708'.encode('utf-8'),mode=AES.MODE_CBC)
bs = aes.encrypt(data)
#b64解码
params = b2a_base64(bs).decode('utf-8')
# params = b64decode(bs)
return params
def get_hotConmments(id):
# print(id)
#提交的信息
data = {
'cursor': '-1',
'offset': '0',
'orderType': '1',
'pageNo': '1',
'pageSize': '20',
'rid': f'R_SO_4_{id}',
'threadId': f'R_SO_4_{id}'
}
post_data = {
'params': get_params(json.dumps(data)),
'encSecKey': get_encSecKey()
}
#获取评论的URL
song_url = 'https://music.163.com/weapi/comment/resource/comments/get?csrf_token=ce10dc34c626dc6aef3e07c86be16d70'
response = requests.post(url=song_url,data=post_data,headers=headers)
# time.sleep(1)
json_dict = json.loads(response.content)
# print(json_dict)
hotcontent = {}
for content in json_dict['data']['hotComments']:
content_text = content['content']
content_id = content['user']['nickname']
hotcontent[content_id] = content_text
return hotcontent
#保存到MySQL数据库
def save_mysql(song_id,song_name,content):
connect = pymysql.Connect(
host='localhost',
port=3306,
user='root',
passwd='root',
db='wangyi',
charset='utf8mb4'
)
cursor = connect.cursor()
# sql = "inster into music_163 velues(%d,'%s','%s','%s')"
sql = """
INSERT INTO music_163(song_id, song_name, comments,nickname)
VALUES(%d, '%s', '%s', '%s')
"""
for nikename in content:
data = (int(song_id),escape_string(song_name),escape_string(content[nikename]),escape_string(nikename))
print(data)
cursor.execute(sql % data)
connect.commit()
if __name__ == '__main__':
get_163()
感谢观看!!!