此次只做技术分享,如有侵权,请联系删除。
首先打开网站发送请求。网址为https://www.ehsy.com/。点击F12,发送请求发现电脑端并没有接口返回数据,即返回切换手机端观察。点解F12观察找到数据接口如下图: 打开一个具体的商品,点击搜索,输入商品对应的价格,观察返回的接口链接,找到存在商品信息的接口。如下图:
观察可以发现这个链接对应能找到关于此商品的基本数据连接,接下来我们对该链接进行分析。
在整个请求headers里面有一个关键的字段值为如图:
很显然这个ehsy-verify字段是一个JS加密后的值,所以我们的重点就在于如何得到这个ehsy-verify值。最简单查找办法就是在搜索框中输入ehsy-verify,然后找到有关的ehsy-verify的JS文件进行分析。
最后找到了这个文件中有关ehsy-verify值得由来,接下来我们将进入源文件中对其进行运行调试。
ehsy-verify的值是由(0, o.aes_gobal)()得来的,所以对其尽心断点调试,然后发现(0, o.aes_gobal)()的值跟两个函数有关如图:
所以对其进行调试运行发现函数p中定义了e,n,i,o,t几个变量,即e的值为(0,a.default)(t),可以看出a.default是另外一个函数,其传入的参数就是t,t的值很容易的得到就是t = ((new Date).getTime() + “”).slice(0, 10),其值就是当前的时间戳进行了下标0-10的切片。那么就开始对a.default函数进行代码反扒,进行断点进入函数。整个过程比较复杂,其嵌套涉及的函数比较多,主要就是找到相关的函数并对其进行处理,我直接列出部分内容:
var ERROR = "input is invalid type"
, WINDOW = "object" === typeof window
, root = WINDOW ? window : {};
root.JS_MD5_NO_WINDOW && (WINDOW = !1);
var WEB_WORKER = !WINDOW && "object" === typeof self
, NODE_JS = !root.JS_MD5_NO_NODE_JS && "object" === typeof process && process.versions && process.versions.node;
NODE_JS ? root = global : WEB_WORKER && (root = self);
var COMMON_JS = !root.JS_MD5_NO_COMMON_JS && "object" === typeof module && module.exports,
// AMD = __webpack_require__("3c35"),
ARRAY_BUFFER = 'True',
HEX_CHARS = "0123456789abcdef".split(""), EXTRA = [128, 32768, 8388608, -2147483648], SHIFT = [0, 8, 16, 24],
OUTPUT_TYPES = ["hex", "array", "digest", "buffer", "arrayBuffer", "base64"],
BASE64_ENCODE_CHAR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""), blocks = [],
buffer8;
Md5.prototype.update = function (t) {
if (!this.finalized) {
var e, n = typeof t;
if ("string" !== n) {
if ("object" !== n)
throw ERROR;
if (null === t)
throw ERROR;
if (ARRAY_BUFFER && t.constructor === ArrayBuffer)
t = new Uint8Array(t);
else if (!Array.isArray(t) && (!ARRAY_BUFFER || !ArrayBuffer.isView(t)))
throw ERROR;
e = !0
}
var i, r, o = 0, a = t.length, s = this.blocks, c = this.buffer8;
while (o < a) {
if (this.hashed && (this.hashed = !1,
s[0] = s[16],
s[16] = s[1] = s[2] = s[3] = s[4] = s[5] = s[6] = s[7] = s[8] = s[9] = s[10] = s[11] = s[12] = s[13] = s[14] = s[15] = 0),
e)
if (ARRAY_BUFFER)
for (r = this.start; o < a && r < 64; ++o)
c[r++] = t[o];
else
for (r = this.start; o < a && r < 64; ++o)
s[r >> 2] |= t[o] << SHIFT[3 & r++];
else if (ARRAY_BUFFER)
for (r = this.start; o < a && r < 64; ++o)
i = t.charCodeAt(o),
i < 128 ? c[r++] = i : i < 2048 ? (c[r++] = 192 | i >> 6,
c[r++] = 128 | 63 & i) : i < 55296 || i >= 57344 ? (c[r++] = 224 | i >> 12,
c[r++] = 128 | i >> 6 & 63,
c[r++] = 128 | 63 & i) : (i = 65536 + ((1023 & i) << 10 | 1023 & t.charCodeAt(++o)),
c[r++] = 240 | i >> 18,
c[r++] = 128 | i >> 12 & 63,
c[r++] = 128 | i >> 6 & 63,
c[r++] = 128 | 63 & i);
else
for (r = this.start; o < a && r < 64; ++o)
i = t.charCodeAt(o),
i < 128 ? s[r >> 2] |= i << SHIFT[3 & r++] : i < 2048 ? (s[r >> 2] |= (192 | i >> 6) << SHIFT[3 & r++],
s[r >> 2] |= (128 | 63 & i) << SHIFT[3 & r++]) : i < 55296 || i >= 57344 ? (s[r >> 2] |= (224 | i >> 12) << SHIFT[3 & r++],
s[r >> 2] |= (128 | i >> 6 & 63) << SHIFT[3 & r++],
s[r >> 2] |= (128 | 63 & i) << SHIFT[3 & r++]) : (i = 65536 + ((1023 & i) << 10 | 1023 & t.charCodeAt(++o)),
s[r >> 2] |= (240 | i >> 18) << SHIFT[3 & r++],
s[r >> 2] |= (128 | i >> 12 & 63) << SHIFT[3 & r++],
s[r >> 2] |= (128 | i >> 6 & 63) << SHIFT[3 & r++],
s[r >> 2] |= (128 | 63 & i) << SHIFT[3 & r++]);
}
return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0,
this.bytes = this.bytes % 4294967296),
this
}
}
function Md5(t) {
if (t)
blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0,
this.blocks = blocks,
this.buffer8 = buffer8;
else if (ARRAY_BUFFER) {
var e = new ArrayBuffer(68);
this.buffer8 = new Uint8Array(e),
this.blocks = new Uint32Array(e)
} else
this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0,
this.finalized = this.hashed = !1,
this.first = !0
}
Md5.prototype.hex = function () {
this.finalize();
var t = this.h0
, e = this.h1
, n = this.h2
, i = this.h3;
return HEX_CHARS[t >> 4 & 15] + HEX_CHARS[15 & t] + HEX_CHARS[t >> 12 & 15] + HEX_CHARS[t >> 8 & 15] + HEX_CHARS[t >> 20 & 15] + HEX_CHARS[t >> 16 & 15] + HEX_CHARS[t >> 28 & 15] + HEX_CHARS[t >> 24 & 15] + HEX_CHARS[e >> 4 & 15] + HEX_CHARS[15 & e] + HEX_CHARS[e >> 12 & 15] + HEX_CHARS[e >> 8 & 15] + HEX_CHARS[e >> 20 & 15] + HEX_CHARS[e >> 16 & 15] + HEX_CHARS[e >> 28 & 15] + HEX_CHARS[e >> 24 & 15] + HEX_CHARS[n >> 4 & 15] + HEX_CHARS[15 & n] + HEX_CHARS[n >> 12 & 15] + HEX_CHARS[n >> 8 & 15] + HEX_CHARS[n >> 20 & 15] + HEX_CHARS[n >> 16 & 15] + HEX_CHARS[n >> 28 & 15] + HEX_CHARS[n >> 24 & 15] + HEX_CHARS[i >> 4 & 15] + HEX_CHARS[15 & i] + HEX_CHARS[i >> 12 & 15] + HEX_CHARS[i >> 8 & 15] + HEX_CHARS[i >> 20 & 15] + HEX_CHARS[i >> 16 & 15] + HEX_CHARS[i >> 28 & 15] + HEX_CHARS[i >> 24 & 15]
Md5.prototype.finalize = function () {
if (!this.finalized) {
this.finalized = !0;
var t = this.blocks
, e = this.lastByteIndex;
t[e >> 2] |= EXTRA[3 & e],
e >= 56 && (this.hashed || this.hash(),
t[0] = t[16],
t[16] = t[1] = t[2] = t[3] = t[4] = t[5] = t[6] = t[7] = t[8] = t[9] = t[10] = t[11] = t[12] = t[13] = t[14] = t[15] = 0),
t[14] = this.bytes << 3,
t[15] = this.hBytes << 3 | this.bytes >>> 29,
this.hash()
}
}
function createOutputMethod(e) {
return new Md5(!0).update(e)[t]()
}
function p() {
var e, n, i, o, t = ((new Date).getTime() + "").slice(0, 10);
return e = createOutputMethod(t),
n = e.split(""),
n.splice(2, 1, "e"),
n.splice(6, 1, "h"),
n.splice(12, 1, 6),
n.splice(25, 1, "b"),
i = n.join("") + t,
o = g(i),
o
}
console.log(p())在这里插入代码片
这样就可以得到函数p的变量e的值,在进行下面的分隔,替换等步骤最终得到变量i的值。然后又将i的值传入到函数g中对其进行加密操作。那么我们就进入到函数g中观察对其i做了那些操作。
function g(e) {
var n = t.default.enc.Utf8.parse(e)
, i = t.default.MD5(s)
, o = t.default.AES.encrypt(n, i, {
mode: t.default.mode.ECB,
padding: t.default.pad.Pkcs7
});
return o.toString()
}
这里t.default.enc.Utf8.parse(e)得到对初入参数e进行了从UTF8编码解析出原始字符串,t.default.MD5(s)观察发现这个s是一个固定值为‘GvcaHhBsKa9kkHmf’,对其进行了MD5加密,接下来先商量并确定好采用的 AES 的 vi (初始变量)、key(秘钥)、mode(加密模式)、padding(填充方式),这里的n就是vi初始变量,i就是key秘钥,对其进行ECB的加密模式以及Pkcs7进行填充。
需要介绍的是一个算法库crypto-js,是谷歌开发的一个纯JavaScript的加密算法类库,可以非常方便的在前端进行其所支持的加解密操作。目前crypto-js已支持的算法有:MD5,SHA-1,SHA-256,AES,Rabbit,MARC4,HMAC,HMAC-MD5,HMAC-SHA1,HMAC-SHA256,PBKDF2。常用的加密方式有MD5和AES。使用时可以引用总文件,也可以单独引用某一文件。安装命令为
npm install crypto-js
安装成功后将文件导入到pycharm中,在js代码中可以通过命令导入:
var CryptoJS = require("crypto-js")
接着替换函数g的代码,最终呈现:
var CryptoJS = require("crypto-js")
function g(e) {
var n = CryptoJS.enc.Utf8.parse(e)
, i = CryptoJS.MD5(s)
, o = CryptoJS.AES.encrypt(n, i, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return o.toString()
}
最后通过运行结果发现能够得到我们需要的ehsy-verify字段的值,如图:
接下来去验证,可以通过postman模拟接口请求或者直接编写爬虫代码验证,我直接编写爬虫代码实现验证。
在得到ehsy-verify字段的值后,编写爬取代码就简单了,观察得到是POST请求,form表单提交请求数据,编写headers请求头,需要pyexecjs模块是python爬虫库里关于javaScript的一套程序,它能帮你解析python代码的js代码。 有经验的爬虫程序员应该知道,在你的请求头中有一部分是被js代码加密的,而这一套js加密程序就保存在你当前访问的网站中(事实上就是存在本地),每一次访问都需要调用js做加密再请求。这个机制可以抵挡大部分的爬虫程序,除非你模仿js加密程序之后再做请求。你可以模拟js程序写一段python程序,也可以直接把网页里的js代码复制下来,使用pyexecjs模块来运用。其最终实现部分爬虫代码如下:
def request_goods_list(self, code):
with open(r'E:\xiyu.js', 'r',encoding='utf-8') as r:
js = r.read()
jsdm = execjs.compile(js)
result = jsdm.call('p') #调用js文件中p函数,得到ehsy-verify值
request_url = "https://m2.ehsy.com/pb/product/sku/desc" #请求接口
headers = {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
# 'Content-Length': '32',
'ehsy-verify':result,
'content-type': 'application/x-www-form-urlencoded',
'Origin': 'https://m.ehsy.com',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-site',
'Referer': 'https://m.ehsy.com /',
'Host': 'm2.ehsy.com',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
}
formdata = dict(skuCode=code, cityId="321", t*o*k*e*n="",watermark="true" )
meta={
"code":code,
}
return scrapy.FormRequest(
method="POST",
url=request_url,
headers=headers,
meta=meta,
formdata=formdata, #表单提交
callback=self.request_product_response,
dont_filter=True
)
最终实现爬取数据如图: