注:本文为转载,转载自link.
需求:爬取某金融区块链网站 https://www.oklink.com/btc/tx-list的数据。
出现问题:请求到数据的ajax的请求头中包含类似如下动态变化且加密的参数
xapiKey:LWIzMWUtNDU0Ny05Mjk5LWI2ZDA3Yjc2MzFhYmEyYzkwM2NjfDI3MTk0ODY0MDUwNzA2Mjk=
解决办法:通过分析浏览器请求到的相关js文件,得到x-apiKey的生成函数,并用python复写。
打开网站,快捷键Ctrl+shift+I打开浏览器工具,选择Network->XHR,刷新网页。
可以看到只有一个请求数据包:
打开之后可以找到请求头:
其中的xapiKey就是请求头中的加密参数,而且每次刷新都会变化。
在右上方找到Search:
输入:x-apiKey,查询到三个结果,如图
可以看到第三个js文件后包含header,打开它。
这里可以点击下面的 {} 将代码格式化:
按快捷键Ctrl+F再次在该js文件里面查找x-apiKey。
定位到x-apiKey之后,将相关联的代码段取出:
function(t, e, n) {
"use strict";
n(115),
n(57),
n(20),
n(60);
function r(t, e) {
for (var n = 0; n < e.length; n++) {
var r = e[n];
r.enumerable = r.enumerable || !1,
r.configurable = !0,
"value"in r && (r.writable = !0),
Object.defineProperty(t, r.key, r)
}
}
var o = new (function() {
function t() {
!function(t, e) {
if (!(t instanceof e))
throw new TypeError("Cannot call a class as a function")
}(this, t),
this.API_KEY = "a2c903cc-b31e-4547-9299-b6d07b7631ab"
}
return function(t, e, n) {
e && r(t.prototype, e),
n && r(t, n)
}(t, [{
key: "encryptApiKey",
value: function() {
var t = this.API_KEY
, e = t.split("")
, n = e.splice(0, 8);
return t = e.concat(n).join("")
}
}, {
key: "encryptTime",
value: function(t) {
var e = (1 * t + 1111111111111).toString().split("")
, n = parseInt(10 * Math.random(), 10)
, r = parseInt(10 * Math.random(), 10)
, o = parseInt(10 * Math.random(), 10);
return e.concat([n, r, o]).join("")
}
}, {
key: "comb",
value: function(t, e) {
var n = "".concat(t, "|").concat(e);
return window.btoa(n)
}
}, {
key: "getApiKey",
value: function() {
var t = (new Date).getTime()
, e = this.encryptApiKey();
return t = this.encryptTime(t),
this.comb(e, t)
}
}]),
t
}())
, i = window.utils.ont
, c = Object.assign({
}, i);
c.interceptors.request.use(function(t) {
return t.url.indexOf("api/explorer/v1") > -1 && (t.headers.common["x-apiKey"] = o.getApiKey()),
t
});
e.a = c
}
很明显,x-apiKey是o通过调用getApiKey()函数得到。
t.headers.common["x-apiKey"] = o.getApiKey()
找到函数定义:
key: "getApiKey",
value: function() {
var t = (new Date).getTime()
, e = this.encryptApiKey();
return t = this.encryptTime(t),
this.comb(e, t)
}
key: "encryptApiKey",
value: function() {
var t = this.API_KEY
, e = t.split("")
, n = e.splice(0, 8);
return t = e.concat(n).join("")
}
变量 t 是一个固定字符串,从上面可以找到
this.API_KEY = "a2c903cc-b31e-4547-9299-b6d07b7631ab"
变量 e 是 对变量 t 进行切片,得到 t 里的每个字符,变量 n 得到 e 里的前8个字符,即:
"a", "2", "c", "9", "0", "3", "c", "c"
最后的变量 t 是 将 e 去掉 n 部分并在后面接上 n ,得到新的字符串返回值
"-b31e-4547-9299-b6d07b7631aba2c903cc"
该值即为getApiKey()函数中comb()函数的 e 参数。
key: "encryptTime",
value: function(t) {
var e = (1 * t + 1111111111111).toString().split("")
, n = parseInt(10 * Math.random(), 10)
, r = parseInt(10 * Math.random(), 10)
, o = parseInt(10 * Math.random(), 10);
return e.concat([n, r, o]).join("")
}
t 是传入的当前时间(13位的时间戳),经过字符串处理交给e;
变量 n , r , o 是产生的0-10的一个随机整数,并结合 e 生成新的关于当前时间和3个随机数的新字符串,比如
"2", "7", "1", "9", "4", "9", "2", "4", "3", "8", "8", "9", "9"]
该值即为getApiKey()函数中comb()函数的 t 参数。
key: "comb",
value: function(t, e) {
var n = "".concat(t, "|").concat(e);
return window.btoa(n)
}
变量 n 为传入参数 t 和 e 处理后得到
2719492921233667|-b31e-4547-9299-b6d07b7631aba2c903cc
window.btoa(n)为base64加密函数,得到加密后的 n
MjcxOTQ5MzI5ODc0MjA2NXwtYjMxZS00NTQ3LTkyOTktYjZkMDdiNzYzMWFiYTJjOTAzY2M=
对比浏览器请求头中的x-apiKey
LWIzMWUtNDU0Ny05Mjk5LWI2ZDA3Yjc2MzFhYmEyYzkwM2NjfDI3MTk0ODc1Mzk0ODMwOTE=
可以看到有较为明显的不一致。
将在浏览器中得到的x-apiKey通过base64解码得到
-b31e-4547-9299-b6d07b7631aba2c903cc|2719487539483091
而通过上述过程得到的加密前的字符串是
2719492921233667|-b31e-4547-9299-b6d07b7631aba2c903cc
可以轻易观察到,我们自己生成的加密前的数据和正确的数据的区别是 " | " 前后的字符串顺序;
经过修改顺序,得到正确结果。
# 获取动态变化且加密的x-apiKey
def get_x_apikey():
# API_KEY固定字符串
API_KEY = "a2c903cc-b31e-4547-9299-b6d07b7631ab"
Key1 = API_KEY[0:8]
Key2 = API_KEY[8:]
# 交换API_KEY部分内容
new_Key = Key2 + Key1
# 获取当前时间,毫秒级
cur_time = round(time.time() * 1000)
# 处理获得的时间
new_time = str(1 * cur_time + 1111111111111)
# 生成三个0-9的随机整数
random1 = str(random.randint(0, 9))
random2 = str(random.randint(0, 9))
random3 = str(random.randint(0, 9))
# 再次处理时间字符串
cur_time = new_time + random1 + random2 + random3
# 将包含API_KEY和时间串的内容合并
this_Key = new_Key + '|' + cur_time
# 转码
n_k = this_Key.encode('utf-8')
# base64加秘
x_apiKey = base64.b64encode(n_k)
# 将加密后的x_apiKey返回
return str(x_apiKey, encoding='utf8')