JS逆向获取网易云音乐评论
前言:
这段时间,一直在研究JS逆向,今天小试牛刀一下,利用JS逆向技术获取网易云音乐评论。
一、分析网页
其实网易云音乐评论的api很好找到的,我们通过F12进入到浏览器(chrome)的开发者模式,因为音乐的评论是动态加载的网页,所以我们可以直接定位到network下的XHR选项,如图:
经过我们一个个的查找分析,定位到get?csrf_token=这个网页,这个里面就是包含评论信息的内容:
可以看出这个网页数据属于json格式的数据,里面包含了评论的内容,评论者的信息,评论的发布时间等:
既然知道了,网页的api在哪,那就好办了,直接请求这个地址就可以了,可是没有这么简单:
从图中可以看出,该api的method是post请求,那么就需要传递post请求的data参数,可以看出该data参数是经过加密的数据,我们必须要找到如何加密这些参数的位置,从而找出如何加密的过程。
我们通过全局搜索params关键字,经过一个个的分析查找,我们找到了加密这些参数的JS网址文件,打开这个JS文件并格式化显示后,继续通过搜索params定位到了加密这些参数的方法的地方:
我们可以看出加过断点的这段JS代码就是我们加密这些参数的方法,可是这里面各种各样的中英文参杂到底是什么意思啊,经过分析我们知道这个window.asrsea这个方法里面有4个参数,但是需要我们获取到4个参数才行,经过断点分析后,我们能获取到这4个参数的具体信息:
这个就是这4个参数的具体信息,第一个参数是一个动态的字典,里面包含的歌曲的id,包含的评论翻页的数,包含了每页评论的条目数,包含了当前的时间戳,其余3个参数都是固定的。因为篇幅有限,具体的JS改写就不在这里过多叙述了。大致的思路就是,通过逆向改写这个JS文件,得到这两个加密的参数,然后通过构建好的参数,对这个api发送请求,获取响应数据,最后提取数据并保存到mongodb数据库。
本案例所用到的模块:
# 导入需要的包
import execjs
import requests
import time
import json
from pymongo import MongoClient
二、构建post请求的data参数
def get_token_params(self):
"""
构建JS传递的参数的方法
:return:
"""
token_param = {
"rid": f"R_SO_4_{self.song_id}",
"threadId": f"R_SO_4_{self.song_id}",
"pageNo": str(self.page),
"pageSize": "20",
"cursor": f"{str(self.timestamp)}",
"offset": "40",
"orderType": "1",
"csrf_token": ""}
return json.dumps(token_param)
def get_post_data(self):
"""
逆向JS并获取post请求的data数据的方法
:return:
"""
token_param = self.get_token_params()
with open('./wyy_comment.js', 'r', encoding='utf-8') as jsfile:
js_code = execjs.compile(jsfile.read())
result = js_code.call('get_token', f'{token_param}')
return {
"params": result.get('encText'),
"encSecKey": result.get('encSecKey'),
}
三、发送请求,获取响应数据
def params_url(self, url):
"""
发送请求,获取响应数据的方法
:param url:
:return:
"""
data = self.get_post_data()
response = requests.post(url, headers=self.headers, data=data)
if response.status_code == 200:
return response.json()
else:
print('网页请求不成功')
四、提取评论的数据信息并保存到mongodb数据库
def get_comment_data(self, json_str):
"""
提取评论数据的方法
:param json_str:
:return:
"""
for json_data in json_str.get('data').get('comments'):
time_temp = time.localtime(json_data.get('time') // 1000)
comment_time = time.strftime("%Y-%m-%d %H:%M:%S", time_temp)
yield {
# 提取评论者名字
'comment_nickname': json_data.get('user').get('nickname'),
# 提取评论者id
'comment_userId': json_data.get('user').get('userId'),
# 提取评论内容
'comment_content': json_data.get('content'),
# 提取评论发布时间
'comment_time': comment_time,
# 提取评论id
'comment_commentId': json_data.get('commentId'),
}
def save_mongo(self, comment_data):
"""
保存到mongodb数据库的方法
:param comment_data:
:return:
"""
self.wyy_comment.insert_one(comment_data)
def run(self):
"""
实现主要逻辑思路的方法
:return:
"""
# 1.发送请求,获取响应数据
json_str = self.params_url(self.API_COMMENT_URL)
# 2.提取音乐评论数据
comment_list = self.get_comment_data(json_str)
for comment_data in comment_list:
# 3.保存数据到mongodb数据库
self.save_mongo(comment_data)
print('保存mongo数据库成功')
五、具体实现思路
def run(self):
"""
实现主要逻辑思路的方法
:return:
"""
# 1.发送请求,获取响应数据
json_str = self.params_url(self.API_COMMENT_URL)
# 2.提取音乐评论数据
comment_list = self.get_comment_data(json_str)
for comment_data in comment_list:
# 3.保存数据到mongodb数据库
self.save_mongo(comment_data)
print('保存mongo数据库成功')
效果展示:
由于JS代码太长了,在这里只展示JS改写后的部分代码:
六、完整代码
# 导入需要的包
import execjs
import requests
import time
import json
from pymongo import MongoClient
class WyyCommentSpider:
"""爬取网易云音乐评论"""
def __init__(self,song_id,page):
self.API_COMMENT_URL = 'https://music.163.com/weapi/comment/resource/comments/get?csrf_token='
self.headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 FS'
}
self.timestamp = int(time.time()) * 1000
client = MongoClient(host='localhost', port=27017)
self.wyy_comment = client['test']['comment']
self.song_id = song_id
self.page = page
def get_token_params(self):
"""
构建JS传递的参数的方法
:return:
"""
token_param = {
"rid": f"R_SO_4_{self.song_id}",
"threadId": f"R_SO_4_{self.song_id}",
"pageNo": str(self.page),
"pageSize": "20",
"cursor": f"{str(self.timestamp)}",
"offset": "40",
"orderType": "1",
"csrf_token": ""}
return json.dumps(token_param)
def get_post_data(self):
"""
逆向JS并获取post请求的data数据的方法
:return:
"""
token_param = self.get_token_params()
with open('./wyy_comment.js', 'r', encoding='utf-8') as jsfile:
js_code = execjs.compile(jsfile.read())
result = js_code.call('get_token', f'{token_param}')
return {
"params": result.get('encText'),
"encSecKey": result.get('encSecKey'),
}
def params_url(self, url):
"""
发送请求,获取响应数据的方法
:param url:
:return:
"""
data = self.get_post_data()
response = requests.post(url, headers=self.headers, data=data)
assert response.status_code == 200
return response.json()
def get_comment_data(self, json_str):
"""
提取评论数据的方法
:param json_str:
:return:
"""
for json_data in json_str.get('data').get('comments'):
time_temp = time.localtime(json_data.get('time') // 1000)
comment_time = time.strftime("%Y-%m-%d %H:%M:%S", time_temp)
yield {
# 提取评论者名字
'comment_nickname': json_data.get('user').get('nickname'),
# 提取评论者id
'comment_userId': json_data.get('user').get('userId'),
# 提取评论内容
'comment_content': json_data.get('content'),
# 提取评论发布时间
'comment_time': comment_time,
# 提取评论id
'comment_commentId': json_data.get('commentId'),
}
def save_mongo(self, comment_data):
"""
保存到mongodb数据库的方法
:param comment_data:
:return:
"""
self.wyy_comment.insert_one(comment_data)
def run(self):
"""
实现主要逻辑思路的方法
:return:
"""
# 1.发送请求,获取响应数据
json_str = self.params_url(self.API_COMMENT_URL)
# 2.提取音乐评论数据
comment_list = self.get_comment_data(json_str)
for comment_data in comment_list:
# 3.保存数据到mongodb数据库
self.save_mongo(comment_data)
print('保存mongo数据库成功')
if __name__ == '__main__':
song_id = input('请输入你需要搜索的歌曲评论的ID:')
for page in range(1,10):
wyy_comment_spider = WyyCommentSpider(song_id,page)
wyy_comment_spider.run()
写在后面的话:
本案例需要了解一些JS逆向方面的知识,了解一点JavaScript基本用法,函数的调用等。另外网易云音乐爬取歌曲,也是像本案例一样,需要逆向出这两个参数,就可以提取出歌曲的真实url地址了,只不过提取歌曲比较麻烦些,但是总的关键都是需要逆向破解加密的参数。