JS逆向爬虫案例分享

某域网站数据爬取之反爬策略JS逆向分析

本次分享解析某域网站数据的反爬机制。

此次只做技术分享,如有侵权,请联系删除。

1、分析网

首先打开网站发送请求,点击F12,发送请求发现电脑端并没有接口返回数据,即返回切换手机端观察。点解F12观察找到数据接口如下图:在这里插入图片描述
打开一个具体的商品,点击搜索,输入商品对应的价格,观察返回的接口链接,找到存在商品信息的接口。如下图:
在这里插入图片描述

观察可以发现这个链接对应能找到关于此商品的基本数据连接,接下来我们对该链接进行分析。
	在整个请求headers里面有一个关键的字段值为如图:

很显然这个ehsy-verify字段是一个JS加密后的值,所以我们的重点就在于如何得到这个ehsy-verify值。最简单查找办法就是在搜索框中输入ehsy-verify,然后找到有关的ehsy-verify的JS文件进行分析。
在这里插入图片描述

最后找到了这个文件中有关ehsy-verify值得由来,接下来我们将进入源文件中对其进行运行调试。

2、JS文件解密分析

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模拟接口请求或者直接编写爬虫代码验证,我直接编写爬虫代码实现验证。

3.、爬取数据

在得到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
        )

最终实现爬取数据如图:

在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值