关键词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&param=%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&param=%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-signx-timestamp每次请求都在变化。

  • 这意味着它们极可能是加密签名参数,可能通过JavaScript动态生成。

第二步:快速定位x-sign来源

我们使用开发者工具的全局搜索功能,搜索关键词x-sign

  • 找到3个结果:一个在DOM元素内,两个在js文件里。

  • DOM元素中出现的不可能用于加密,直接忽略。

  • 剩下两个js文件index-v108.min.jsindex-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 + "&param=" + 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 + "&param=" + 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&param=%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跨语言协作的深度学习之旅,希望这篇博客对你们有所帮助。

最后再次提醒:

技术中立,请各位合理合法地使用爬虫工具,切勿滥用!出了问题咱可不负责哟~

愿你我爬虫技术之路愉快又充实,下次再见!