在构造爬虫请求时,通过浏览器抓包发现请求参数中带了一个sign参数:
使用requests
包构造爬虫请求,发现如果缺少这个sign字段或者sign计算的不正确,则只能得到“invalid signature”的服务端返回结果,无法得到正确的结果。因此还是只能研究一下sign是如何计算出来的。
通过观察sign的字符串大致能够猜到,是用了类似于md5的一种加密计算方式,加密用到的字段可能包括发送的messages,以及发送的“time”时间戳字段。之后,在服务端使用相同加密方式计算哈希值,如果结果一致才能正确返回结果,不一致则会返回错误。
要计算哈希值,肯定这段函数是放在js里的,所以我们在开发者工具中进入到“调试器”,然后全局搜索我们抓包的API路径:
这样就能定位到发送请求的那一段js代码,而这段代码中肯定就包含请求中的“sign”字段是从哪个变量取的,之后我们就可以看到“sign”字段的变量是如何被计算出来的。
为了能够实现跳转函数定义,我们可以把整个js文件拷贝到vscode中,然后格式化代码,这样读起来更容易。定位到最终计算哈希值的函数:
可以看到sign字段是由时间戳+message字段计算的一个SHA256哈希值,为了能在爬虫代码中调用这段函数得到sign字段,我们需要安装一个python第三方库,用于在python代码中执行js函数:
pip install PyExecJS
不过,我们需要先注意到,这段js函数是异步的(async),也就是如果直接调用函数的话,你其实拿不到任何返回值,js中想要获取异步函数的值,都是通过函数执行完成之后的回调实现的,而我们要在python代码中执行js的话,显然是没法回调的。所以我们最佳的办法是不用异步函数,想办法寻找同步函数作为替代。
分析这段js代码可以看到,异步的地方主要在于crypto.subtle
包,所以上网一搜,有一个同步的函数也能达到同样的效果,于是我们的js代码就可以变为:
var crypto = require('crypto');
function sha256(content) {
// https://stackoverflow.com/questions/57626477/using-javascript-crypto-subtle-in-synchronous-function
return crypto.createHash('sha256').update(content).digest('hex')
}
function getSign(t, msg) {
n = {}.PUBLIC_SECRET_KEY,
a = `${t}:${msg}:${n}`;
sign = sha256(a)
return sign
};
之后,在python代码中调用这段js函数,传入时间戳和message参数,然后获取函数返回结果:
import execjs
import time
js_sha = '''
var crypto = require('crypto');
function sha256(content) {
return crypto.createHash('sha256').update(content).digest('hex')
}
function getSign(t, msg) {
n = {}.PUBLIC_SECRET_KEY,
a = `${t}:${msg}:${n}`;
sign = sha256(a)
return sign
};
'''
time_now = int(time.time())
msg = "XXXX"
signature = execjs.compile(js_sha).call('getSign', time_now, msg)
之后,在requests请求的表单数据中加入计算得到的sign参数,就能得到服务端的正常返回了。