文章目录
- 1. 写在前面
- 2. 接口分析
- 3. 爬虫实现
【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
1. 写在前面
分析的主要是搜索接口的三个参数(ts、rs、signature)前面两个参数不需要过多的分析,一个是时间戳、一个是16位的随机字符(会参与签名运算)!我们主要看signature参数
在几个月前作者曾分析过它的签名算法,当时其实作者自己也有一些产品负面舆情信息的收集需求。时隔多月再次尝试运行之前程序得到了下面的异常信息:
{'result': {'status': {'code': 10005, 'msg': '操作失败'}, 'timestamp': 'Sat Sep 14 14:04:43 +0800 2024', 'data': []}}
推测大概率是算法变动导致生成的加密参数不对,改动应该不大,经过梳理发现以前的时候只有page页码去参与签名、现在新增了关键词,如下所示:
2. 接口分析
老规矩,直接登录自己的某博账号,去到投诉的入口进行搜索,查看接口的发包信息,如下所示:
直接看rs参数,我们可以根据上面的var g = u([l, p, b, h, c, d[“type” + e]].sort().join(“”));这一行代码去展开分析,其中参数p就是rs参数,我们可以往上翻找生成的位置,如下所示:
p = function (e, t, r) {
var n = ""
, i = t
,
a = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
e && (i = Math.round(Math.random() * (r - t)) + t);
for (var o = 0; o < i; o++) {
n += a[Math.round(Math.random() * (a.length - 1))]
}
return n
}(!1, 16)
p函数的主要功能生成一个长度可控的随机字母跟数字组合的字符串。 !1 表示 false,意味着不需要生成随机长度的字符串,而是固定生成长度为16的字符串
接下来,解决最重要的签名参数signature,u方法进行签名,l参数是时间戳,p的动态16位随机字符串算法我们也还原了,接下来继续看b参数,这个参数是一个固值,如下所示:
参数h则是关键词,签名的时候我们直接传进来即可!最后还有一个动态参数d[“type” + e]
这个参数是请求接口页码数值,更多的翻页采集则需要对应传入
核心的JS加密算法均在下图中,webpack的压缩,如果扣算法的话就需从这里开始入手。扣取对应的算法以及模块导出,如下所示:
如果说要去扣原生网站的Webpack的话,最终的代码量会很大,过程可能也会繁琐一些。其实最终的签名参数就是我们前面分析出来的那几个参数拼接做了一个SHA-256,如下所示:
在这里,我们可以简单写一个脚本对上面调试环境中已经拼接好的字符结果进行哈希运算来验证一下。代码如下所示:
import hashlib
# 需要进行SHA-256加密的字符串
input_string = "$d6eb7ff91ee257475%11017262212207888Vs6Syp78Pr6OBAk咸鱼之王"
# 创建SHA-256对象
sha256_obj = hashlib.sha256()
# 将字符串编码为字节流后进行加密
sha256_obj.update(input_string.encode('utf-8'))
# 获取SHA-256的16进制结果
sha256_hash = sha256_obj.hexdigest()
# 输出SHA-256结果
print(f"SHA-256加密结果: {sha256_hash}")
# OUT: 13c3a83745668b46af0a2ab7f2c4ba1450cb25f4d5bd329f389dcad3f79a031f
计算的结果跟发包参数一模一样!那么至此上面梳理出来的思路已经很清晰,接下来可以直接避免再去扣取又臭又长JS代码,直接可选择自己拿手的语言来实现签名算法
如果你是一位新手朋友,不会算法的还原也没有关系。作者帮你还原好了,完整的JS纯算代码还原如下所示:
const crypto = require('crypto');
function sha256Hash(input) {
return crypto.createHash('sha256').update(input).digest('hex');
}
function get_signature(keyword, page) {
// 获取当前时间戳
const u = new Date().getTime();
// 生成随机字符串
function generateRandomString(e, t, r) {
let n = "";
let i = t;
const a = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (e) {
i = Math.round(Math.random() * (r - t)) + t;
}
for (let o = 0; o < i; o++) {
n += a[Math.round(Math.random() * (a.length - 1))];
}
return n;
}
const l = generateRandomString(false, 16);
// 将输入参数排序并连接成字符串
const inputStr = [u, l, '$d6eb7ff91ee257475%', keyword, 10, page].sort().join("");
// 使用 SHA-256 算法生成签名
const signature = sha256Hash(inputStr);
return {
'ts': u,
'rs': l,
'signature': signature
};
}
3. 爬虫实现
最终我们使用Python编写完整的搜索抓取程序,代码如下所示:
# _*_ coding: utf-8 _*_
import time
import requests
import execjs
from loguru import logger
# sign.js就是上面还原的算法代码,自行保存即可
def load_js(file_path="sign.js"):
with open(file_path, "r", encoding="utf-8") as js_file:
js_code = js_file.read()
return execjs.compile(js_code)
def generate_signature(keyword, page, js_context):
return js_context.call('gen_sign', keyword, page)
def perform_search(keyword, page, js_context):
cookies = {} # 自行获取
headers = {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
"cache-control": "no-cache",
"pragma": "no-cache",
"priority": "u=1, i",
"referer": "https://cq.tousu.sina.com.cn/index/search/?keywords=%E5%92%B8%E9%B1%BC%E4%B9%8B%E7%8E%8B&t=1",
"sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"128\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": :"" # 自行获取,
"x-requested-with": "XMLHttpRequest"
}
signature_data = generate_signature(keyword, page, js_context)
logger.info(f'抓取第 {page} 页.')
params = {
'ts': signature_data['ts'],
'rs': signature_data['rs'],
'signature': signature_data['signature'],
'keywords': keyword,
'page_size': '10',
'page': str(page),
}
response = requests.get('https://tousu.sina.com.cn/api/index/s', cookies=cookies, params=params, headers=headers)
return response.json()
def process_search_results():
js_context = load_js()
for page in range(1, 20):
results = perform_search('咸鱼之王', page, js_context)
logger.info(f'搜索结果: {results}')
if __name__ == '__main__':
process_search_results()
至此,运行正如我们开篇的截图一样!提前祝各位中秋佳节愉快