关键词:
python爬虫
、js逆向
温馨提示:技术无罪,爬取适度,别搞事,报警了可别找我~
本期目标:长安CS75的用户真实评论
今天我们选择了易车网作为“受害者”,爬取一下长安CS75车型的所有用户评论数据,用以数据分析或者其他用途。
目标网址:
https://dianping.yiche.com/changancs75/koubei/
一、抓包找数据——探寻真实数据接口
我们先打开目标网站,准备动手抓包:
鼠标右键页面,选择
检查
进入开发者工具(一般是F12快捷键)
进入
网络(Network)
标签页,然后刷新页面(F5
键)
经过一番耐心观察,我们发现一个链接非常可疑,可能就是我们的目标数据接口:
https://mapi.yiche.com/information_api/api/v1/point_comment/query_comment_page_list?cid=508¶m=%7B%22tagId%22%3A%22-10%22%2C%22currentPage%22%3A%221%22%2C%22serialId%22%3A%223987%22%2C%22pageSize%22%3A20%7D
点开预览一看,好家伙!果然评论数据都在这里头:
二、动手抓取——先试探,发现有玄机
于是乎,我们赶紧用Python试一把:
import requests
url = 'https://mapi.yiche.com/information_api/api/v1/point_comment/query_comment_page_list?cid=508¶m=%7B%22tagId%22%3A%22-10%22%2C%22currentPage%22%3A%221%22%2C%22serialId%22%3A%223987%22%2C%22pageSize%22%3A20%7D'
headers = {
'accept': '*/*',
'content-type': 'application/json;charset=UTF-8',
'origin': 'https://dianping.yiche.com',
'referer': 'https://dianping.yiche.com/changancs75/koubei/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
'x-city-id': '3101',
'x-ip-address': '183.65.30.58',
'x-platform': 'pc',
'x-sign': 'f6e9c9f9b2dcf9680c572c86f907dbd7',
'x-timestamp': '1742043928317',
'x-user-guid': '732cc1670dec29d43ef3c176c1f900ef',
}
response = requests.get(
url = url,
headers = headers,
)
print(response.text)
一顿操作猛如虎,然而一跑发现:竟然签名校验失败!
冷静一下,仔细观察报错,我们发现请求头里面有几个奇怪的字段:
x-timestamp
x-sign
每次刷新这俩字段都会变化,这可疑得很,猜测可能使用了动态生成加密参数。
headers = {
"x-sign": "f6e9c9f9b2dcf9680c572c86f907dbd7", # 动态加密值
"x-timestamp": "1742043928317" # 13位时间戳
}
三、逆向分析——搞定x-sign
加密参数
第一步:分析请求头中的加密参数
在开发者工具的“网络”面板,反复刷新页面。
发现
x-sign
和x-timestamp
每次请求都在变化。这意味着它们极可能是加密签名参数,可能通过JavaScript动态生成。
第二步:快速定位x-sign
来源
我们使用开发者工具的全局搜索功能,搜索关键词x-sign
:
找到3个结果:一个在DOM元素内,两个在
js
文件里。DOM元素中出现的不可能用于加密,直接忽略。
剩下两个
js
文件index-v108.min.js
和index-v311.min.js
很可疑,必须重点关注!
我们依次在这两个js
文件中找到x-sign
,并且打上断点:
第二步:确定真实加密位置
刷新页面后,成功命中断点,发现有多次断住。
对比请求链接,发现第二次断点与我们目标接口链接完全匹配,说明找对地方了。
第二步:深入js代码,定位核心加密函数
进入断点位置的js代码,发现生成
x-sign
参数的是s(e, t)
这个函数。仔细查看发现,它的返回值
s
正是我们需要的x-sign
!再继续分析,发现这里还调用了另一个函数
r(e, t)
,为了保险,我们继续打断点跟进去,彻底将加密过程理清楚了。
第三步:构造JavaScript加密代码,提取并改写为可调用的形式
经过分析,我们发现
s(e, t)
函数就是生成x-sign
的关键所在。将整个函数
s(e, t)
以及相关函数r(e, t)
全部复制过来,稍微修改一下
还发现需要进行
md5编码
因此我们调用AI帮我们快速实现了一个js版的
md5编码
函数。
且编写一个入口函数
main()
以便Python调用。
function s(e, t) {
var n = "";
if ("headers" == e.encryptType) {
var i = e.data ? JSON.stringify(e.data) : "{}"
, o = r(e, t);
n = "cid=" + t.cid + "¶m=" + i + o + t.timestamp
} else {
var a = [];
a.push("cid=" + t.cid),
a.push("uid=" + t.uid),
a.push("ver=" + t.ver),
a.push("devid=" + (e.deviceId || "")),
a.push("t=" + t.timestamp),
a.push("key=" + t.paramsKey),
n = a.join(";")
}
var s = md5Encode(n);
return {
xsign:s,
timestamp: t.timestamp
}
}
function main(){
return s(e, t)
}
运行发现出值了
需要nodejs环境
整理好后,保存为
ycw.js
文件,供后续Python调用。
ycw.js
const CryptoJS = require('crypto-js');
// 定义一个函数来进行 MD5 编码
function md5Encode(str) {
// 使用 CryptoJS.MD5 方法对输入的字符串进行 MD5 编码
const md5Hash = CryptoJS.MD5(str);
// 将编码结果转换为十六进制字符串
return md5Hash.toString();
}
e = {
"url": "//mapi.yiche.com/information_api/api/v1/point_comment/query_comment_page_list",
"data": {
"tagId": "-10",
"currentPage": "1",
"serialId": "3987",
"pageSize": 20
},
"headers": {
"x-platform": "pc"
},
"method": "GET",
"withCredentials": "true",
"async": "true",
"isParam": "true",
"dataType": "json",
"defaultContentType": "true",
"encryptType": "headers",
"isEncrypt": "false",
"isBrush": "false",
"proxy": "false",
"timeout": 5000
}
t = {
"cid": "508",
"ver": "v11.4.0",
"timestamp": Date.now(),
"gradeParam": {},
"uid": "",
"headerEncryptKeys": [
{
"name": "pc",
"value": "19DDD1FBDFF065D3A4DA777D2D7A81EC",
"cid": "508"
},
{
"name": "phone",
"value": "DB2560A6EBC65F37A0484295CD4EDD25",
"cid": "601"
},
{
"name": "h5",
"value": "745DFB2027E8418384A1F2EF1B54C9F5",
"cid": "601"
},
{
"name": "business_applet",
"value": "64A1071F6C3C3CC68DABBF5A90669C0A",
"cid": "601"
},
{
"name": "wechat",
"value": "AF23B0A6EBC65F37A0484395CE4EDD2K",
"cid": "601"
},
{
"name": "tencent",
"value": "1615A9BDB0374D16AE9EBB3BBEE5353C",
"cid": "750"
}
],
"paramsKey": "f48aa2d0-31e0-42a6-a7a0-64ba148262f0"
}
function r(e, t) {
if (!e.headers || !e.headers["x-platform"])
return t.cid;
var n = t.headerEncryptKeys.find(function(t) {
return t.name == e.headers["x-platform"]
});
return n ? n.value : "DB2560A6EBC65F37A0484295CD4EDD25"
}
function s(e, t) {
var n = "";
if ("headers" == e.encryptType) {
var i = e.data ? JSON.stringify(e.data) : "{}"
, o = r(e, t);
n = "cid=" + t.cid + "¶m=" + i + o + t.timestamp
console.log(t.timestamp)
} else {
var a = [];
a.push("cid=" + t.cid),
a.push("uid=" + t.uid),
a.push("ver=" + t.ver),
a.push("devid=" + (e.deviceId || "")),
a.push("t=" + t.timestamp),
a.push("key=" + t.paramsKey),
n = a.join(";")
}
var s = md5Encode(n);
return {
xsign:s,
timestamp: t.timestamp
}
}
function main(){
return s(e, t)
}
三、Python调用JS代码,成功破解加密机制!
通过Python的execjs或js2py模块读取我们整理好的js加密文件。
执行js代码,成功获得
x-sign
和时间戳。
import execjs
# 读取 JavaScript 文件
with open('ycw.js', 'r', encoding='utf-8') as file:
js_code = file.read()
# 编译并执行 JavaScript 代码
ctx = execjs.compile(js_code)
# 假设 JavaScript 文件中定义了一个名为 add 的函数
result = ctx.call('main')
填充到请求头中,继续执行爬虫代码,成功返回数据!
headers = {
'accept': '*/*',
'content-type': 'application/json;charset=UTF-8',
'origin': 'https://dianping.yiche.com',
'referer': 'https://dianping.yiche.com/changancs75/koubei/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
'x-city-id': '3101',
'x-ip-address': '183.65.30.58',
'x-platform': 'pc',
'x-sign': str(result.get('xsign')),
'x-timestamp': str(result.get('timestamp')),
'x-user-guid': '732cc1670dec29d43ef3c176c1f900ef',
}
最终执行成功,效果完美,拿到了我们想要的数据了:
爬取.py
import requests
import execjs
import time
# 读取 JavaScript 文件
with open('ycw.js', 'r', encoding='utf-8') as file:
js_code = file.read()
# 编译并执行 JavaScript 代码
ctx = execjs.compile(js_code)
# 假设 JavaScript 文件中定义了一个名为 add 的函数
result = ctx.call('main')
url = 'https://mapi.yiche.com/information_api/api/v1/point_comment/query_comment_page_list?cid=508¶m=%7B%22tagId%22%3A%22-10%22%2C%22currentPage%22%3A%221%22%2C%22serialId%22%3A%223987%22%2C%22pageSize%22%3A20%7D'
headers = {
'accept': '*/*',
'content-type': 'application/json;charset=UTF-8',
'origin': 'https://dianping.yiche.com',
'referer': 'https://dianping.yiche.com/changancs75/koubei/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
'x-city-id': '3101',
'x-ip-address': '183.65.30.58',
'x-platform': 'pc',
'x-sign': str(result.get('xsign')),
'x-timestamp': str(result.get('timestamp')),
'x-user-guid': '732cc1670dec29d43ef3c176c1f900ef',
}
response = requests.get(
url = url,
headers = headers,
)
print(response.text)
四、验证数据结果
运行结果后,长安CS75所有评论信息成功返回!我们开心地看到了心仪已久的json数据!
总结心得——知识点回顾与注意事项
请求接口分析:准确定位数据真实接口
js逆向技巧
:
开发者工具调试能力
打断点,逐步分析js代码
快速锁定动态生成的关键请求头参数
跨语言调用
:
python与javascript的巧妙融合,实现动态参数破解
注意事项
:
爬虫有风险,数据抓取需适度,尊重目标网站规则。
本文所提供技术方案仅供学习交流,切勿用于违法用途。
五、小结与心得
本次我们不仅成功获取了易车网关于长安CS75的所有用户评论数据,还深入掌握了如何破解动态加密参数的生成方法。
这不仅仅是一次爬虫实战,更是对web数据爬取、js逆向分析及python与javascript跨语言协作的深度学习之旅,希望这篇博客对你们有所帮助。
最后再次提醒:
技术中立,请各位合理合法地使用爬虫工具,切勿滥用!出了问题咱可不负责哟~
愿你我爬虫技术之路愉快又充实,下次再见!