爬虫&逆向--Day25&Day26--京东h5st案例解析

案例地址链接:https://search.jd.com/Search?keyword=白酒\&enc=utf-8\&pvid=53c39759079d4b92a3e6d3ea71974571

案例爬取链接:https://api.m.jd.com/api?appid=search-pc-java\&t=1759021222491\&client=pc\&clientVersion=1.0.0\&cthr=1\&uuid=1759021192300961326017\&loginType=3\&keyword=白酒\&functionId=pc_search_searchWare\&body={"enc":"utf-8","pvid":"53c39759079d4b92a3e6d3ea71974571","area":"1_72_55652_0","page":1,"s":1}\&x-api-eid-token=jdd03PNQPCXFNEW775N652WCJ7SFKNDCIVT3QCO7HIPIDZZQHITB6EHU2T7RJP4RAAXX474SZMRFKEOLKRYZDT5V4O4UONEAAAAMZRXKU7EIAAAAAC7FTSK4VTJR3OYX\&h5st=20250928090024497;zm6g9g393pqjh336;f06cc;tk05w2f6134c941lMysxWXRhVG8yfZBUuVeH3caFu9uV68uIrt7DhhRhIrJsXabO_UOUYk9VCMaIW4sV8g_VqNeV8AeI;5079a9c20f5d8b943e2e27c6305df7d9;5.2;1759021222497;gt6f-JuVuB7HwgqVoVuIoF7U046ZB5_ZxI7ZBh-f1BeZnZ-G_U7ZBh-f1ZfIpR7V98OVuV7U8ULIpNLUAQOIrR_Utd7Vvd7V8c7IqZfZnZfFbwrI-MrE-hfZXx-Z9UuJwJuJ8M_J7ULIoRLTxdrUvJ_VvdbT_UuJwd_VqN_ZB5_Zuc7EzcrJ-hfZXx-ZxZfZnZfUsY7ZBh-f1ZfVzZ_WsJqK8wLH7kMU5YfZnZ-E-hfZXx-Z8YuHv98UwheV-h-T-trG9oLJvYfZB5hW-ZuVz8rM-h-T-JbF-hfZXxPCBh-f-J7Q-h-T-VOVsY7ZBhfZB5hWvh-T-dOVsY7ZBhfZB5hWtdeZnZfVwN6J-hfZBh-f1BOWB5_ZvdOE-YfZBhfZXx-ZNIqGLcbVuYOPPQaGuYfZnZPGyQ7GAY6ZBhfZB5hWxh-T-BOE-YfZBhfZXxfVB5_ZqN6J-hfZBh-f1d_VB5_ZrN6J-hfZBh-f1heZnZPUsY7ZBhfZB5hWxh-T-ROE-YfZBhfZXxvUth-T-VOE-YfZBhfZXx-ZrpPVzh_ZB5_ZwN6J-hfZBh-f1heZnZvHqYfZBhfZXxPUB5_Zuw7ZBhfZB5hWxh-T-x7ZBhfZB5hWxh-T-RrE-hfZBh-fmg-T-R7G8QaD8YfZB5hWkgfZXZPItR_JrJuJAMOV-YOUCQ7H-h-T-ZeF-hfZBh-fmg-T-haF-hfZXx-ZtJeDB1eUrpLHKgvTxpfVwhfMTgvFqkbIz8rM-h-T-dLEuYfZB5xD;81cf5c0c0b8d93f9a2363ab39520e008;gRaW989Gy8bE_oLE7w-Gy8rFvM7MtoLI4wrJ1R6G88bG_wPD9k7J1RLHxgKJ\&t=1759021222505

一、案例【京东载荷h5st字段】

1.1、入口定位

这次的目标很明确,我们就是需要破解京东的搜索接口的h5st字段,首先我们先通过关键字进行搜索,搜索h5st

首先我们先找到我们的目标url复制到【https://curlconverter.com/】中生成基础爬虫代码

很明显,人家是做了加密处理,根本不给你返回任何的数据,所以我们只有破解h5st才可以获取到数据

1.2、代码分析

点击进入到代码内部

进来以后这里就两行代码,其中第二行【return tP.resolve(_Ga);】就是包装,把这个带有h5st的值直接包装到promise中去,所以关键点还是第一行代码【var Ga = this._sdnmd(Gf)】中的this._sdnmd($Gf)就生成了h5st了

【这一步直接出结果就和promise没关系了,下一步就是包装promise】

window.PSign.sign(d).then(res=>{console.log(res)})

这行代码会生成一个promise,所以我们执行上面的这行代码就会生成promise的结果

1.3、扣JS

因为主要是this._sdnmd(_Gf); 这句代码生成的h5st所以把这句代码直接扣走

参数_Gf是直接传递来的,所以我们拼接_Gf,进行测试,缺什么补什么

由此可得d就是参数_$Gf,所以进行拼接即可

在扣完代码拼接完paramsH5sign和params以后,我们就需要确定调用this._sdnmd中的this 其实就是window.ParamsSign = _Gk,

所以我们可以进行替换,

参数加密处理的逻辑就是,先对参数params进行SHA256处理,然后把处理完的结果放到paramsH5sign.body中,然后在对paramsH5sign参数进行处理生成h5st

所以我们替换完this以后,我们需要找到window.ParamsSign

通过判断是new出来的,所以我们只需要扣try中的正常处理逻辑即可

当把所有的代码都扣出来完成以后,允许就会报window找不到,就补window

这次又报document找不到,我们可以在补document,补完了又会报Element

这样其实我们第一阶段就已经完成了,下面我们就需要开始链式补环境

当我们把window和document还有Element补完以后,结果就会出来了,就有h5st这个结果了,但是这个结果是错误的,在python中运行是得不到结果的,因为没过服务器的检测点,并且这个和h5st的长度也没关系,因为这个代码中他们做了原型链的检测,所以和长度什么的没关系,

所以下面我们还是需要分析原型链关系,根据原型链关系进行补环境

看这个div标签的属性和方法,在属性和方法中找它的原型链关系

通过查看可以得到Element继承Node ,Node继承EventTarget, EventTarget 继承Object

如果提示报错,我们不知道如何处理的时候,我们就复制报错的地方,在源码中找到,断点定位到该处,进行判断是什么报错找不到,处理使用代码以外还需要会断点调试什么的,进行查错,

1.4、补环境【 补环境框架,V8引擎】

有的时候在源码中,他们会判断,Element是否有,并且有的时候也会判断Element的原型链它的父亲是否有,Element它的爷爷是否有,所以我们需要通过原型链去补环境,而不能直接简单的补一下

复制代码
// 补环境
window = {}
window.document = {}
function Element(){
    
}

有些网站它不检测原型链,我们按照上面的方式补环境是可以的,但是当有些网站检测原型链的时候,我们在按照上面的基础补环境就不行了

以上是一段准备代码,方便我们通过链式补环境,

复制代码
// 二、补环境
// (1) window对象处理
global_val = globalThis   // globalThis  其实就是global  就是node中的顶级变量
// 给global加一个window属性哈  给global全局配一个window属性   给global顶级变量挂载一个全局变量window
Object.defineProperty(global_val, "window", {
    get: function () {
        return global_val
    },
    set: function set_window(val) {
        global_val = val
    },
    enumerable: true,
    configurable: true,

})
// 无论代码调取那个都是指向这个window  做一个全局处理
window.top = window.self = window.window = window

以上是对window缺少找不到进行的补环境处理

小window --》 大Window --》 WindowProperties --》 EventTarget --》 Object

小window其实是来自于大Window的一个实例化对象,小window的原型对象其实就是大Window

所以我们如果都补全了最好就是四层,目前我们该案例就写了两层

复制代码
// 四层都不齐全
createConstructor("EventTarget", true, [], {})
createConstructor("WindowProperties", true, [], {},"EventTarget" )
createConstructor("Window", true, [], {},"WindowProperties" )
// 只补两层
createConstructor("Window", true, [], {})

window.document 报document找不到,因为document中的东西太多,所以我们先简单的补一下

复制代码
window.document = {} 先跳过

我们先window.document = {} 跳过document ,先处理Element

Element 首字母大写的,我们都可以当作是类来处理,查看它的原型链

直接dir(Element),查看一下它爹,它爷这些原型链即可

复制代码
/*
* Element --》 Node --》 EventTarget --》 Object
* 因为Element的父类是Node,Node的父类是EventTarget,EventTarget的父类Object,而且在createConstructor方法中,每次在最后面也只能添加一层的父类
* 所以我们给Element设置完父类Node以后,还需要给Node以同样的方式设置父类EventTarget  连续三个createConstructor,这才完成了一个原型串
* */
createConstructor("EventTarget", true, [], {})   // EventTarget 后面就没爹了,没爹不用写,默认是Object
createConstructor("Node", true, ["parentNode", "childNodes"], {}, "EventTarget")
createConstructor("Element", true, ["childElementCount", "innerHTML"], {}, "Node")

到这里,已经没有任何缺少环境的信息提示了,报一个看不懂对象的错误,所以这里我们就需要去添加监控了

添加完监控以后,就会吐很多关于window相关的信息,但是这些window相关的信息都没什么用

所以我们还需要监听另外的一个,需要监控document。

实例化出错了

因为我们刚开始肯定是不知道,那个需要补,那个不需要补,但是现在假装我们已经知道了需要补那个环境,所以我们就挑重点的来补

首先我们看到document调用了all

所以我们还是先dir(document.all)

复制代码
// 补document的爹和爷  document --》 HTMLDocument  --》 Document --》 node
createConstructor("Document", true, [], {}, "Node")
createConstructor("HTMLDocument", true, [], {}, "Document")
复制代码
// 这里是document实例化的时候传入的一些属性
    // all:new HTMLAllCollection(null,null,"ljc")  其实这样就实例化一个对象就可以了,但是还有一种可能是document.all.xxx 所以我们也需要在实例化的同时也监控一下
    // 所以我们就watch(new HTMLAllCollection(null,null,"ljc"),谁调用的呀)document.all.xxx,也就被监控了
    all:watch(new HTMLAllCollection(null,null,"ljc"),"document.all")

下一个就是dir(document.documentElement)

复制代码
// 补document.documentElement  HTMLHtmlElement --》 HTMLElement  --》 Element --》 Node --》 EventTarget --》 Object
createConstructor("HTMLElement", true, [], {},"Element")
createConstructor("HTMLHtmlElement", true, [], {},"HTMLElement")
复制代码
//按照上面的方式补全documentElement
    documentElement:watch(new HTMLHtmlElement(null,null,"ljc"),"document.documentElement")

下面在补一个cookie属性

下一个dir(document.body)

复制代码
// 补document.body   HTMLBodyElement -->  HTMLElement  -->  Element  -->  Node  --> EventTarget --> Object
createConstructor("HTMLBodyElement", true, [], {}, "HTMLElement")
复制代码
//补body
    body: watch(new HTMLBodyElement(null, null, "ljc"), "document.body")

到目前为止,document的属性就都补完了,现在开始补方法了

经过dir查看他们的链接关系,发现这些方法都在document这个类中,所以这些方法在那个类中,我们就应该补到那个类中

其中第一个优先补script

我们需要先查看他的链式关系

复制代码
//补script
createConstructor("HTMLScriptElement", true, [], {}, "HTMLElement")
//补parentNode
createConstructor("HTMLHeadElement", true, [], {}, "HTMLElement")
复制代码
createElement: function (tagName) {
        // 打印一下tagName 看一下创建的是那个标签
        console.log("Document prototype  createElement ", tagName)

        if (tagName === "script") {
            // 因为script后面也有可能调用了别的,所以我们在这里也需要进行监控
            script = watch(new HTMLScriptElement({
                parentNode: watch(new HTMLHeadElement(null, null, "ljc"), "script的parentNode")
            }, null, "ljc"), "script对象")
            return script
        }

        // canvas和上面的script就一样了
        if (tagName === "canvas") {
            return {}
        }


    },

到这里,这个环境中已经有了script标签,虽然script调用这个parentNode找不到,但是至少不在报错了

所以我们这里就需要找这个parentNode是什么东西

因为这个ele是咱们自己创建的,没有放到任何的一个父级标签中,所以ele.parent是null

因为这个srcipt在调用parent之前调用了什么,比如text/javascript,所以这个script标签都在head中,但是body中可能也有,所以我们就需要多在源码中搜索一下确定一下

所以我们在补这个parent的时候,这个script.parentNode就是那个head标签,因为script标签在head中,所以我们就需要确定这个head标签中的第0元素,然后确定它的创建类,来创建这个parentNode

再往下就是补canvas 但是这个canvas可补,可不补,所以不需要关注

然后这个时候在运行,需要提前登录在运行,就会得到h5st 长度是556位,目前这个值是可以成功的

把paramsH5sign作为参数传递进去,然后通过开通的接口就会生成h5st,所以以后在想拿h5st就不需要在和Python打交道了直接做成一个接口,之前用Python拿数据都是使用import execjs或者import subprocess 之前都是用Python调度js代码,如果有了定时器什么的就比较麻烦,所以现在就直接把JS生成接口,如果Python要用,就直接自己传参自己拿结果,访问一次获取一个h5st,每次获取的h5st还都不一样,因为是时间作为了参数获取的h5st

1.5、代码文件

该案例需要导入:pip install curl_cffi npm install express

1.5.1、Python文件

复制代码
import hashlib
from curl_cffi import requests
import time
import os



def sha256_hash(data: str) -> str:
    """
    计算字符串的 SHA-256 哈希值
    :param data: 输入字符串
    :return: 十六进制表示的哈希值字符串
    """
    hash_obj = hashlib.sha256(data.encode('utf-8'))
    return hash_obj.hexdigest()


cookies = {
    '__jdv': '143920055|direct|-|none|-|1759198339989',
    '__jdu': '1759198339988240393195',
    '3AB9D23F7A4B3CSS': 'jdd0336CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4EAAAAMZTBSF5TAAAAAADED2AUUGHUOS44X',
    'mba_muid': '1759198339988240393195',
    'wlfstk_smdl': 'di09prnlss3k3bnvofmpwmkjo812881l',
    'TrackID': '1RVWFVwuwegh6iaq2dUaSUyxtlv2W8ZsWmxP21tBeuzA6JLP01W25RW4ZNKGWhDFKBR_xMLfaTohtTorBRBpdNlxBRXHQ87UD-YA5CjBNN54',
    'thor': '2DD9CF08CB217E996764A091B245EB29B43DEECE308DCF74BCBC66126340B7CC57EDD320176A72EA74043C4CDD7F4324D872C1E3813F23CFC94796AE1ABFB72BC0EB76990CBC9F42DD82C52859CD62E4DF2374343ED9606AFB4437FA8D8FACB1753AD87ACE74EDE1B970E782C73FFEFB25EBC28E00E7D303C43E5B5FDC1C3826210EFE4E1F9CC0F684515F5703EB90D7302414E086525715F186A95323BEAE81',
    'light_key': 'AASBKE7rOxgWQziEhC_QY6yaKYl0-nkwHhCq7N-FMTwrP69jFjUruev7Rsm2eNMxzUBY6VIP',
    'pinId': 'w8Ih7LOJnxwxhZalSpUGsbV9-x-f3wj7',
    'pin': 'jd_52b15ac66b44e',
    'unick': '%E5%91%86%E5%91%867115',
    'ceshi3.com': '203',
    '_tp': 'oRxO98xexSXw5ZlTXC2fbDrx5URIy94AUL5RT79KEbs%3D',
    '_pst': 'jd_52b15ac66b44e',
    '__jdc': '143920055',
    'areaId': '5',
    'shshshfpa': 'b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380',
    'shshshfpx': 'b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380',
    'ipLoc-djd': '1-72-55653-0',
    'shshshfpb': 'BApXSoJBtm_xAUGY1ek_5MROtpLvWQ2uQBhtYhK9o9xJ1MhEHxo62',
    '3AB9D23F7A4B3C9B': '36CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4E',
    'flash': '3_6gpgATZhoG6k8czgYn2HnA7xPO3mh2cXAmEzCQ0z422kecKRpx89Biu_-2dtCY3V_jARZ4wIxT3UKzcCONXY1gr97YLyPbCHYjhWNhvMGQGBq_IB6Udnm-kw105qUMUkmMKnu5lY-1pCuNNxyfD0jxFU2_-xm1QimzdddFQTJfmsemqUvJBa3V**',
    '__jda': '143920055.1759198339988240393195.1759198340.1759198340.1759200581.2',
    '__jdb': '143920055.1.1759198339988240393195|2.1759200581',
    'sdtoken': 'AAbEsBpEIOVjqTAKCQtvQu179H4TgU1sQiwY_jgIlLkfzub2EneuGP2sLpz488hVNgAiBKppDnR-9KGdRFMb2fGttHfYI6x6ZfMbttuK65GA90pBEalHNm_-LbXYXth3my7jHZ5D0UqmCIA',
}

headers = {
    'accept': 'application/json, text/plain, */*',
    'accept-language': 'zh-CN,zh;q=0.9',
    'origin': 'https://search.jd.com',
    'priority': 'u=1, i',
    'referer': 'https://search.jd.com/',
    'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-site',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
    'x-referer-page': 'https://search.jd.com/Search',
    'x-rp-client': 'h5_1.0.0',
    # 'cookie': '__jdv=143920055|direct|-|none|-|1759198339989; __jdu=1759198339988240393195; 3AB9D23F7A4B3CSS=jdd0336CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4EAAAAMZTBSF5TAAAAAADED2AUUGHUOS44X; mba_muid=1759198339988240393195; wlfstk_smdl=di09prnlss3k3bnvofmpwmkjo812881l; TrackID=1RVWFVwuwegh6iaq2dUaSUyxtlv2W8ZsWmxP21tBeuzA6JLP01W25RW4ZNKGWhDFKBR_xMLfaTohtTorBRBpdNlxBRXHQ87UD-YA5CjBNN54; thor=2DD9CF08CB217E996764A091B245EB29B43DEECE308DCF74BCBC66126340B7CC57EDD320176A72EA74043C4CDD7F4324D872C1E3813F23CFC94796AE1ABFB72BC0EB76990CBC9F42DD82C52859CD62E4DF2374343ED9606AFB4437FA8D8FACB1753AD87ACE74EDE1B970E782C73FFEFB25EBC28E00E7D303C43E5B5FDC1C3826210EFE4E1F9CC0F684515F5703EB90D7302414E086525715F186A95323BEAE81; light_key=AASBKE7rOxgWQziEhC_QY6yaKYl0-nkwHhCq7N-FMTwrP69jFjUruev7Rsm2eNMxzUBY6VIP; pinId=w8Ih7LOJnxwxhZalSpUGsbV9-x-f3wj7; pin=jd_52b15ac66b44e; unick=%E5%91%86%E5%91%867115; ceshi3.com=203; _tp=oRxO98xexSXw5ZlTXC2fbDrx5URIy94AUL5RT79KEbs%3D; _pst=jd_52b15ac66b44e; __jdc=143920055; areaId=5; shshshfpa=b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380; shshshfpx=b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380; ipLoc-djd=1-72-55653-0; shshshfpb=BApXSoJBtm_xAUGY1ek_5MROtpLvWQ2uQBhtYhK9o9xJ1MhEHxo62; 3AB9D23F7A4B3C9B=36CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4E; flash=3_6gpgATZhoG6k8czgYn2HnA7xPO3mh2cXAmEzCQ0z422kecKRpx89Biu_-2dtCY3V_jARZ4wIxT3UKzcCONXY1gr97YLyPbCHYjhWNhvMGQGBq_IB6Udnm-kw105qUMUkmMKnu5lY-1pCuNNxyfD0jxFU2_-xm1QimzdddFQTJfmsemqUvJBa3V**; __jda=143920055.1759198339988240393195.1759198340.1759198340.1759200581.2; __jdb=143920055.1.1759198339988240393195|2.1759200581; sdtoken=AAbEsBpEIOVjqTAKCQtvQu179H4TgU1sQiwY_jgIlLkfzub2EneuGP2sLpz488hVNgAiBKppDnR-9KGdRFMb2fGttHfYI6x6ZfMbttuK65GA90pBEalHNm_-LbXYXth3my7jHZ5D0UqmCIA',
}

# 访问的接口
url = "https://api.m.jd.com/api"

# body数据 就是一个json数据 有第几页   在这里修改page
body = "\\{\"page\":1,\"area\":\"7_527_35108_56983\",\"pvid\":\"c7edfed410bb4172956eb62ce0ad72f4\"\\}"
# 去我们通过express开放的接口去获取h5st
response = requests.post("localhost:3000/api/data", json={
    "params": {"appid": "search-pc-java",
               "functionId": "pc_search_adv_Search",
               "body": sha256_hash(body)   # 把这个body做了一下sha256
               }
})

param = response.json()["result"]
print("param:::",len(param['h5st']))

params = {
    "appid": "search-pc-java",
    "t": [
        str(param["t"]),
        str(int(time.time() * 1000))
    ],
    "client": "pc",
    "clientVersion": "1.0.0",
    "uuid": "1745841960334352365307",
    "keyword": "手机",   # 在这里修改类型
    "functionId": "pc_search_getShopAndWare",
    "body": body,
    "x-api-eid-token": "jdd03QJJ7DOUYP7T5O2IKSRFQANXZJYHALCU3ECRYXYVSULUXN7DODVWDAGUVYK2WLOTISQ3XQ7U7G5PP57CC2QFRLQFA5AAAAAMYYUR7TVIAAAAACORWVTOFTVSAUQX",
    "h5st": param["h5st"]
}
response = requests.get(url, headers=headers, cookies=cookies, params=params, impersonate='chrome101')

print(response.text)
print(response)

1.5.2、express文件

复制代码
// 引入 express
const express = require('express');
const app = express();
const fs = require('fs');
const { get_h5st } = require('./04 h5st');
ljc_log = console.log;

// 支持 JSON 请求体解析
app.use(express.json());

// 一个 GET 接口
app.get('/api/hello', (req, res) => {
  res.json({ message: 'Hello from Node.js backend!' });
});

// 一个 POST 接口
app.post('/api/data', (req, res) => {
  // eval(fs.readFileSync('myserver\\jingdong\\5.2.0\\env.js', 'utf8'))
  ljc_log('接收到数据:', req.body["params"]);
  res.json({ status: 'success', result: get_h5st(req.body["params"]) });
});

// 监听端口
const PORT = 3000;
app.listen(PORT, () => {
  console.log(`✅ Server is running at http://localhost:${PORT}`);
});

/*
* express 就是编程里面的web框架  就是对外开一个接口
* npm install express  需要安装一下这个
* */

1.5.3、JS文件

以上是:一、补环境2个工具 在任何的案例中都可以拿过去用

以上是:三、源码

复制代码
cryptoJs = require("crypto-js")


// 一、补环境2个工具  在任何的案例中都可以拿过去用
// (1)监控函数
// ***************************第一个补环境工具**************************
// (2)函数构造器
/**
 * 创建具有特定属性和方法的构造函数
 * 该函数主要用于浏览器环境模拟,可以创建具有严格访问控制、继承支持和原型方法的构造函数
 * 通过外部数据存储和Symbol键实现实例数据隔离,提高安全性和防检测能力
 *
 * @param {string} constructorName - 构造函数的名称,将作为全局变量挂载到window对象上
 * @param {boolean} enableStrictMode - 是否启用严格模式验证,启用后需要特定令牌才能调用构造函数
 * @param {Array} [propertiesList=[]] - 属性定义列表,支持简单字符串属性或自定义属性描述符
 *        - 简单属性:字符串形式,如 "name"
 *        - 自定义属性:数组形式,如 ["all", {get: function() {...}}]
 * @param {Object} [prototypeMethods={}] - 要添加到原型上的方法
 * @param {string} [parentConstructorName=null] - 父构造函数的名称,用于实现继承
 * @returns {Function} 返回新创建的构造函数,同时会挂载到window对象上
 *
 * @example
 * // 创建简单的Person构造函数
 * createConstructor("Person", true, ["name", "age"], {
 *   greet: function() { return `Hello, ${this.name}`; }
 * });
 *
 * function Person(){
 *     this.name = name
 *     this.age = age
 *     greed:function(){
 *       return `Hello, ${this.name}`;
 *     }
 * }
 *
 *
 * @example
 * // 创建继承自Animal的Dog构造函数
 * createConstructor("Dog", true, ["breed"], {
 *   bark: function() { return "Woof!"; }
 * }, "Animal");
 *
 * function Dog(){    这个Dog继承Animal
 *     this.name = name
 *     this.age = age
 *     bark: function() {
 *          return "Woof!";
 *     }
 * }
 */
function createConstructor(constructorName, enableStrictMode, propertiesList = [], prototypeMethods = {}, parentConstructorName = null) {
    // 使用对象存储所有实例的数据,实现数据隔离
    // 每个实例通过Symbol键关联到其数据,提高安全性
    const instancesData = {};

    // 创建构造函数
    const Constructor = function (element, propertySetter, validationToken) {
        // 验证构造函数调用合法性
        if (enableStrictMode && !(validationToken && validationToken === "ljc")) {
            throw new Error("Illegal constructor");
        }

        // 调用父构造函数(如果存在)
        if (parentConstructorName && window[parentConstructorName]) {
            window[parentConstructorName].call(this, element, null, "ljc");
        }

        // 设置对象属性
        if (propertySetter && typeof propertySetter === "function") {
            propertySetter(this);
        }

        // 初始化元素属性
        const instanceProperties = element && typeof element === "object" ? {...element} : {};

        // 创建唯一标识符
        this._element = Symbol('_element');
        instancesData[this._element] = instanceProperties;
    };

    // 设置继承关系
    if (parentConstructorName && window[parentConstructorName]) {
        Constructor.prototype = Object.create(window[parentConstructorName].prototype);
        Constructor.prototype.constructor = Constructor;
        Constructor.__proto__ = window[parentConstructorName];
    }

    // 设置toStringTag
    Object.defineProperty(Constructor.prototype, Symbol.toStringTag, {
        value: constructorName,
        writable: false,
        enumerable: false,
        configurable: true
    });

    // 添加原型方法
    Object.keys(prototypeMethods).forEach(methodName => {
        Constructor.prototype[methodName] = prototypeMethods[methodName];
    });

    // 将构造函数挂载到全局对象
    window[constructorName] = Constructor;

    return Constructor;
}


// 二、补环境
// (1) window对象处理
global_val = globalThis   // globalThis  其实就是global  就是node中的顶级变量
// 给global加一个window属性哈  给global全局配一个window属性   给global顶级变量挂载一个全局变量window
Object.defineProperty(global_val, "window", {
    get: function () {
        return global_val
    },
    set: function set_window(val) {
        global_val = val
    },
    enumerable: true,
    configurable: true,

})
// 无论代码调取那个都是指向这个window  做一个全局处理
window.top = window.self = window.window = window

// (2)补原型链环境
/* 补大Window
* 小window和大Window是两个东西,小window类似于是大Window的实例化对象,大Window是类的概念
* */
// 创建Window空间   createConstructor 一般处理的都是大Window这样的类对象,小window一半都是我们自己构建
createConstructor("Window", true, [], {})
console.log(Window)
// 小window需要和大Window建立父子关系
window.__proto__ = Window.prototype
// 给小window补toStringTag方法  因为小window是单独处理的,所以需要单独把这些加进来
Object.defineProperty(window, Symbol.toStringTag, {
    value: "Window",   // '[object Window]'  因为是这个字符串所以需要返回Window
    writable: false,
    enumerable: false,
    configurable: true
});

window.document = {}


/* 补Element
* Element --》 Node --》 EventTarget --》 Object
* 因为Element的父类是Node,Node的父类是EventTarget,EventTarget的父类Object,而且在createConstructor方法中,每次在最后面也只能添加一层的父类
* 所以我们给Element设置完父类Node以后,还需要给Node以同样的方式设置父类EventTarget  连续三个createConstructor,这才完成了一个原型串
* */
createConstructor("EventTarget", true, [], {})   // EventTarget 后面就没爹了,没爹不用写,默认是Object
createConstructor("Node", true, ["parentNode", "childNodes"], {}, "EventTarget")
createConstructor("Element", true, ["childElementCount", "innerHTML"], {}, "Node")


// 补document的爹和爷  document --》 HTMLDocument  --》 Document --》 node  经过查看链,这些方法都在Document中,所以就补在Document类中
createConstructor("Document", true, [], {
    createElement: function (tagName) {
        // 打印一下tagName 看一下创建的是那个标签
        console.log("Document prototype  createElement ", tagName)

        if (tagName === "script") {
            // 因为script后面也有可能调用了别的,所以我们在这里也需要进行监控
            script = watch(new HTMLScriptElement({
                parentNode: watch(new HTMLHeadElement(null, null, "ljc"), "script的parentNode")
            }, null, "ljc"), "script对象")
            return script
        }

        // canvas和上面的script就一样了
        if (tagName === "canvas") {
            return {}
        }


    },
    createEvent: function () {

    },
    querySelector: function () {

    },
    getElementsByTagName: function () {

    },
}, "Node")
createConstructor("HTMLDocument", true, [], {}, "Document")




// 补document.all 的爹直接就是Object
createConstructor("HTMLAllCollection", true, [], {})

// 补document.documentElement  HTMLHtmlElement --》 HTMLElement  --》 Element --》 Node --》 EventTarget --》 Object
createConstructor("HTMLElement", true, [], {}, "Element")
createConstructor("HTMLHtmlElement", true, [], {}, "HTMLElement")

// 补document.body   HTMLBodyElement -->  HTMLElement  -->  Element  -->  Node  --> EventTarget --> Object
createConstructor("HTMLBodyElement", true, [], {}, "HTMLElement")

//补script
createConstructor("HTMLScriptElement", true, [], {}, "HTMLElement")
//补parentNode
createConstructor("HTMLHeadElement", true, [], {}, "HTMLElement")


// (3)添加监控
// 监控document
// window.document = watch({},"document")   给document对象先置空{},然后在监控起来,监控的名字是"document",在返回window.document
//如果案例不检测原型链,我们就可以使用基础补环境直接documenti = {}  按照上面的先置空,然后在监控,但是本案例对原型链进行了检测,
// 所以我们需要先通过dir(document)先找到它爹它爷,然后补HTMLDocument --》Document --》 node这样的原型链出来, 最后再用创建的爹HTMLDocument new一个document对象出来
window.document = watch(new HTMLDocument({
    // 这里是document实例化的时候传入的一些属性
    // all:new HTMLAllCollection(null,null,"ljc")  其实这样就实例化一个对象就可以了,但是还有一种可能是document.all.xxx 所以我们也需要在实例化的同时也监控一下
    // 所以我们就watch(new HTMLAllCollection(null,null,"ljc"),谁调用的呀)document.all.xxx,也就被监控了
    all: watch(new HTMLAllCollection(null, null, "ljc"), "document.all"),

    //按照上面的方式补全documentElement
    documentElement: watch(new HTMLHtmlElement(null, null, "ljc"), "document.documentElement"),

    //在补一个cookie属性
    cookie: '__jdv=143920055|direct|-|none|-|1759198339989; __jdu=1759198339988240393195; 3AB9D23F7A4B3CSS=jdd0336CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4EAAAAMZTBSF5TAAAAAADED2AUUGHUOS44X; _gia_d=1; xapieid=jdd0336CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4EAAAAMZTBSF5TAAAAAADED2AUUGHUOS44X; mba_muid=1759198339988240393195; mba_sid=17591983546191142823231.1; wlfstk_smdl=di09prnlss3k3bnvofmpwmkjo812881l; TrackID=1RVWFVwuwegh6iaq2dUaSUyxtlv2W8ZsWmxP21tBeuzA6JLP01W25RW4ZNKGWhDFKBR_xMLfaTohtTorBRBpdNlxBRXHQ87UD-YA5CjBNN54; pinId=w8Ih7LOJnxwxhZalSpUGsbV9-x-f3wj7; pin=jd_52b15ac66b44e; unick=%E5%91%86%E5%91%867115; ceshi3.com=203; _tp=oRxO98xexSXw5ZlTXC2fbDrx5URIy94AUL5RT79KEbs%3D; __jda=143920055.1759198339988240393195.1759198340.1759198340.1759198340.1; __jdb=143920055.4.1759198339988240393195|1.1759198340; __jdc=143920055; o2State=; is_avif=onAVIF; areaId=5; shshshfpa=b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380; shshshfpx=b9a6c825-8808-1785-a164-cdcc6d85da4a-1759198380; cn=160; ipLoc-djd=1-72-55653-0; shshshfpb=BApXSoJBtm_xAUGY1ek_5MROtpLvWQ2uQBhtYhK9o9xJ1MhEHxo62; sdtoken=AAbEsBpEIOVjqTAKCQtvQu17mfvlXCqDS0kDr1URwxQHljkUa_9A_ZlGxFL5FumtPOEc2qL3IJkeuNb1O6o04CUR4DMk0etLSPh823yKNu4WG_MO2ZAQTa1qerWUv7eZjfgraGRgUhS_8NI; 3AB9D23F7A4B3C9B=36CXCJW76HVLG5NN3HX7RRXSOAKV7PM4O2R2CEWREEYEERJ3MGNRRZZL6AKZQDEG6JSLQTBE5CCGNYDHCKPS4VKR4E',

    //补body
    body: watch(new HTMLBodyElement(null, null, "ljc"), "document.body"),

}, null, "ljc"), "document")
// 监控window,名字是"window" 重新用window   重新用的名字 = watch(监控对象, 监控的名字)
window = watch(window, "window")

// 三、源码
// ***************************此处补充源码**************************

// 四、测试
// 使用源码进行new,通过实例化创建window.PSign对象
/*window.PSign = new window.ParamsSign({
    appId: 'f06cc',
    preRequest: false,
    onSign: (res) => {
        // 签名可用率监控,业务方自行上报
        if (res.code != 0) {
            try {
                window.dra &&
                window.dra.sendCustomEvent &&
                window.dra.sendCustomEvent({
                    name: 'main_search',
                    metrics: {
                        error_code: '751',
                        error_type_txt: '接口加密失败onSign非0',
                    },
                    context: {
                        error_code: res.code,
                    },
                })
            } catch (error) {
                console.log(error)
            }
        }
    },
    onRequestTokenRemotely: (res) => {
        // 算法接口可用率监控,业务方自行上报
        if (res.code != 200) {
            try {
                window.dra &&
                window.dra.sendCustomEvent &&
                window.dra.sendCustomEvent({
                    name: 'main_search',
                    metrics: {
                        error_code: '751',
                        error_type_txt: '接口加密失败onRequestTokenRemotely',
                    },
                    context: {
                        error_msg: res && res.message ? res.message : '接口加密失败',
                    },
                })
            } catch (error) {
                console.log(error)
            }
        }
    },
})

const paramsH5sign = {
    appid: 'search-pc-java',
    functionId: "pc_search_adv_Search",
    client: 'pc',
    clientVersion: '1.0.0',
    t: new Date().getTime(),
}
params = {
    "ad_ids": "292:6",
    "xtest": "new_search",
    "ec": "utf-8",
    "area": "1",
    "page": "2",
    "simpleSearch": "0"
}
paramsH5sign.body = cryptoJs.SHA256(JSON.stringify(params))

// 使用实例化创建的对象,替换this
ret = window.PSign._$sdnmd(paramsH5sign);
console.log(":::::", ret)*/


function main(paramsH5sign) {
    //取源码
    // ***************************此处补充源码**************************
    // new 实例化
    encrypt = new ParamsSignMain({
        appId: "fb5df",
        debug: false,
        onSign: function (t) {
        },
        onRequestToken: function (t) {
        },
        onRequestTokenRemotely: function (t) {
        },
        bucket: "0.1.8"
    })
    // 传进来的 paramsH5sign 只在这里进行了取值操作
    result = encrypt._$sdnmd({
        "appid": paramsH5sign['appid'],//"item-v3",
        "functionId": paramsH5sign['functionId'],//"pc_stocks",
        "client": "pc",
        "clientVersion": "1.0.0",
        "t": +new Date(),
        "body": paramsH5sign['body']
    })
    console.log("H5ST:::", result["h5st"].length, result)
    document_all = 0
    return result
}

//  module.exports js的导出,导出那个函数,导出main这个函数,对外叫什么名字,对外叫get_h5st
module.exports = {get_h5st: main}
相关推荐
蒋星熠18 小时前
爬虫与自动化技术深度解析:从数据采集到智能运维的完整实战指南
运维·人工智能·爬虫·python·深度学习·机器学习·自动化
livingbody1 天前
【2025年9月版 亲测可用】《人民日报》PDF文件下载
开发语言·爬虫·python·pdf
zybsjn3 天前
爬虫访问第三方 HTTPS 网站时遇到的 SSL 异常处理
爬虫
两只好3 天前
Scrapy 重构新选择:scrapy_cffi 快速上手教程
爬虫
深蓝电商API3 天前
爬虫的“Cookie”管理艺术:维持登录状态与会话
爬虫
B站_计算机毕业设计之家4 天前
大数据 Python小说数据分析平台 小说网数据爬取分析系统 Django框架 requests爬虫 Echarts图表 17k小说网 (源码)✅
大数据·爬虫·python·机器学习·数据分析·可视化·小说
zhengjianyang&1234 天前
美团滑块-[behavior] 加密分析
javascript·经验分享·爬虫·算法·node.js
深蓝电商API5 天前
实战:爬取豆瓣电影Top250,并生成Excel榜单
爬虫·python·excel
爱学习的徐徐5 天前
Python 豆瓣TOP250 爬虫类讲解
爬虫·python