import hashlib import json import random import re import time import urllib from abc import ABC, abstractmethod from urllib.parse import urlparse from curl_cffi import requests class JsTkBase(ABC): def __init__(self): self._eid = "" self._token = "" def _get_sign(self, text: str) -> str: text_bytes = text.encode("utf-8") md5_str = hashlib.md5(text_bytes).hexdigest() return md5_str def get_token(self): return self._token def get_eid(self): return self._eid def _td_encrypt(self, e): """ 将输入 e 按照给定的JS逻辑进行 JSON化 -> URL编码 -> 自定义Base64编码 -> 加斜杠 Args: e: 输入的数据 (可以是字典、列表、字符串、数字等,会被JSON序列化) Returns: str: 编码后的字符串,末尾带 "/" """ # 1. 自定义字母表,对应 Base64 的 64 个字符 # JS中的 'u' 是 0-63 的索引对应的字符 custom_alphabet = ( "23IL> 2) & 63 # 索引 2: byte1 的后 2 位 + byte2 的前 4 位 idx2 = (byte1 & 3) << 4 if byte2 is not None: idx2 |= (byte2 >> 4) & 15 # else: 如果 byte2 不存在,这里只使用了 byte1 的后 2 位,高位补 0 # 索引 3: byte2 的后 4 位 + byte3 的前 2 位 idx3 = 64 # 默认填充 if byte2 is not None: idx3 = (byte2 & 15) << 2 if byte3 is not None: idx3 |= (byte3 >> 6) & 3 # else: 如果 byte3 不存在,只使用了 byte2 的后 4 位,高位补 0 # else: 如果 byte2 不存在,保持填充状态 64 # 索引 4: byte3 的后 6 位 idx4 = 64 # 默认填充 if byte3 is not None: idx4 = byte3 & 63 # else: 如果 byte3 不存在,保持填充状态 64 # 根据索引查找字符或使用填充符 encoded_string += custom_alphabet[idx1] encoded_string += custom_alphabet[idx2] if idx3 == 64: encoded_string += padding_char else: encoded_string += custom_alphabet[idx3] if idx4 == 64: encoded_string += padding_char else: encoded_string += custom_alphabet[idx4] # 5. 在末尾添加 "/" return encoded_string + "/" def _request(self, headers: dict, data: dict): response = requests.post( "https://jra.jd.com/jsTk.do", headers=headers, data=data ) result = response.json() self._token = result.get("data", {}).get("token") self._eid = result.get("data", {}).get("eid") @abstractmethod def generate_token(self, user_agent: str, cookie: str = "") -> str: pass class NormalJsTk(JsTkBase): def __init__(self): super().__init__() self.__biz_id = "gold_m" self.__o = "m.jd.com/" def generate_token(self, user_agent: str, cookie: str = ""): ctime = int(time.time() * 1000) app_version = user_agent.replace("Mozilla/", "") d_param = { "ts": { "deviceTime": ctime, "deviceEndTime": ctime + random.randint(50, 150), }, "ca": { "tdHash": "af7c21fe564a8cf177fcc77ce8847eb0", "contextName": "webgl,experimental-webgl", "webglversion": "WebGL 1.0 (OpenGL ES 2.0 Chromium)", "shadingLV": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", "vendor": "WebKit", "renderer": "WebKit WebGL", "extensions": [ "ANGLE_instanced_arrays", "EXT_blend_minmax", "EXT_clip_control", "EXT_color_buffer_half_float", "EXT_depth_clamp", "EXT_disjoint_timer_query", "EXT_float_blend", "EXT_frag_depth", "EXT_polygon_offset_clamp", "EXT_shader_texture_lod", "EXT_texture_compression_bptc", "EXT_texture_compression_rgtc", "EXT_texture_filter_anisotropic", "EXT_texture_mirror_clamp_to_edge", "EXT_sRGB", "KHR_parallel_shader_compile", "OES_element_index_uint", "OES_fbo_render_mipmap", "OES_standard_derivatives", "OES_texture_float", "OES_texture_float_linear", "OES_texture_half_float", "OES_texture_half_float_linear", "OES_vertex_array_object", "WEBGL_blend_func_extended", "WEBGL_color_buffer_float", "WEBGL_compressed_texture_astc", "WEBGL_compressed_texture_etc", "WEBGL_compressed_texture_etc1", "WEBGL_compressed_texture_pvrtc", "WEBGL_compressed_texture_s3tc", "WEBGL_compressed_texture_s3tc_srgb", "WEBGL_debug_renderer_info", "WEBGL_debug_shaders", "WEBGL_depth_texture", "WEBGL_draw_buffers", "WEBGL_lose_context", "WEBGL_multi_draw", "WEBGL_polygon_mode", ], "wuv": "Google Inc. (Apple)", "wur": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", }, "m": {"compatMode": "CSS1Compat"}, "n": { "vendorSub": "", "productSub": "20030107", "vendor": "Google Inc.", "maxTouchPoints": 0, "doNotTrack": "1", "pdfViewerEnabled": True, "hardwareConcurrency": 10, "cookieEnabled": True, "appCodeName": "Mozilla", "appName": "Netscape", "appVersion": app_version, "platform": "MacIntel", "product": "Gecko", "userAgent": user_agent, "language": "zh-CN", "onLine": True, "webdriver": False, "javaEnabled": False, "deprecatedRunAdAuctionEnforcesKAnonymity": False, "deviceMemory": 8, "enumerationOrder": [ "vendorSub", "productSub", "vendor", "maxTouchPoints", "scheduling", "userActivation", "doNotTrack", "geolocation", "connection", "plugins", "mimeTypes", "pdfViewerEnabled", "webkitTemporaryStorage", "webkitPersistentStorage", "windowControlsOverlay", "hardwareConcurrency", "cookieEnabled", "appCodeName", "appName", "appVersion", "platform", "product", "userAgent", "language", "languages", "onLine", "webdriver", "getGamepads", "javaEnabled", "sendBeacon", "vibrate", "deprecatedRunAdAuctionEnforcesKAnonymity", "protectedAudience", "bluetooth", "storageBuckets", "clipboard", "credentials", "keyboard", "managed", "mediaDevices", "storage", "serviceWorker", "virtualKeyboard", "wakeLock", "deviceMemory", "userAgentData", "login", "ink", "mediaCapabilities", "devicePosture", "hid", "locks", "gpu", "mediaSession", "permissions", "presentation", "serial", "usb", "xr", "adAuctionComponents", "runAdAuction", "canLoadAdAuctionFencedFrame", "canShare", "share", "clearAppBadge", "getBattery", "getUserMedia", "requestMIDIAccess", "requestMediaKeySystemAccess", "setAppBadge", "webkitGetUserMedia", "clearOriginJoinedAdInterestGroups", "createAuctionNonce", "joinAdInterestGroup", "leaveAdInterestGroup", "updateAdInterestGroups", "deprecatedReplaceInURN", "deprecatedURNToURL", "getInstalledRelatedApps", "getInterestGroupAdAuctionData", "registerProtocolHandler", "unregisterProtocolHandler", ], }, "p": [ {"name": "PDF Viewer"}, {"name": "Chrome PDF Viewer"}, {"name": "Chromium PDF Viewer"}, {"name": "Microsoft Edge PDF Viewer"}, {"name": "WebKit built-in PDF"}, ], "w": {"devicePixelRatio": 2, "screenTop": 0, "screenLeft": 0}, "s": { "availHeight": 1080, "availWidth": 1920, "colorDepth": 30, "height": 1080, "width": 1920, "pixelDepth": 30, }, "sc": {}, "ss": { "cookie": True, "localStorage": True, "sessionStorage": True, "globalStorage": False, "indexedDB": True, }, "tz": -480, "lil": "", "wil": "", "wi": { "ow": 1920, "oh": 1080, "iw": 274, "ih": 959, "etn": "[object External]", }, } headers = { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9", "cache-control": "no-cache", "content-type": "application/x-www-form-urlencoded;charset=UTF-8", "origin": "https://shop.m.jd.com", "pragma": "no-cache", "priority": "u=1, i", "referer": "https://shop.m.jd.com/", "sec-ch-ua-mobile": "?1", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "user-agent": user_agent, } data = { "d": self._td_encrypt(d_param), } a_param = { "pin": "", "oid": "", "bizId": self.__biz_id, "fc": self._eid if self._eid else "", "mode": "strict", "p": "s", "fp": "6d2c8c60af349b1a87ddd3b065194ca4", "ctype": "1", "v": "4.2.8.0", "pv": "02_mt_5LXK_60653970589", "f": "3", "s": self._get_sign(data["d"] + "_*_UYBN6YGTNO6DHPVB"), "o": self.__o, "qs": "", "jsTk": "", "qi": "", } data["a"] = self._td_encrypt(a_param) response = self._request(headers, data) print(response) class TxSMJsTk(JsTkBase): def __init__(self): super().__init__() self.__url = "https://txsm-m.jd.com/?babelChannel=ttt35" self.__biz_id = "jdtxsm" self.__jd_risk_token_id = "" self.__jd_jr_td_risk_pin = "" self.__o = "txsm-m.jd.com/" def get_jd_risk_token_id(self): return self.__jd_risk_token_id def get_jd_jr_td_risk_pin(self): return self.__jd_jr_td_risk_pin def generate_risk_pin(self, user_agent: str, cookie: str): headers = { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9", "cookie": cookie, "user-agent": user_agent, } response = requests.get("https://gia.jd.com/m.html", headers=headers) text = response.text.replace(" ", "") token = re.findall(r"jd_risk_token_id='(.*?)';", text) if token: self.__jd_risk_token_id = token[0] # 正则表达式解析后面 token = re.findall(r"jd_jr_td_risk_pin='(.*?)';", text) if token: self.__jd_jr_td_risk_pin = token[0] def generate_token(self, user_agent: str, cookie: str = ""): self.generate_risk_pin(user_agent, cookie) ctime = int(time.time() * 1000) app_version = user_agent.replace("Mozilla/", "") d_param = { "ts": { "deviceTime": ctime, "deviceEndTime": ctime + random.randint(50, 150), }, "ca": { "tdHash": "af7c21fe564a8cf177fcc77ce8847eb0", "contextName": "webgl,experimental-webgl", "webglversion": "WebGL 1.0 (OpenGL ES 2.0 Chromium)", "shadingLV": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", "vendor": "WebKit", "renderer": "WebKit WebGL", "extensions": [ "ANGLE_instanced_arrays", "EXT_blend_minmax", "EXT_clip_control", "EXT_color_buffer_half_float", "EXT_depth_clamp", "EXT_disjoint_timer_query", "EXT_float_blend", "EXT_frag_depth", "EXT_polygon_offset_clamp", "EXT_shader_texture_lod", "EXT_texture_compression_bptc", "EXT_texture_compression_rgtc", "EXT_texture_filter_anisotropic", "EXT_texture_mirror_clamp_to_edge", "EXT_sRGB", "KHR_parallel_shader_compile", "OES_element_index_uint", "OES_fbo_render_mipmap", "OES_standard_derivatives", "OES_texture_float", "OES_texture_float_linear", "OES_texture_half_float", "OES_texture_half_float_linear", "OES_vertex_array_object", "WEBGL_blend_func_extended", "WEBGL_color_buffer_float", "WEBGL_compressed_texture_astc", "WEBGL_compressed_texture_etc", "WEBGL_compressed_texture_etc1", "WEBGL_compressed_texture_pvrtc", "WEBGL_compressed_texture_s3tc", "WEBGL_compressed_texture_s3tc_srgb", "WEBGL_debug_renderer_info", "WEBGL_debug_shaders", "WEBGL_depth_texture", "WEBGL_draw_buffers", "WEBGL_lose_context", "WEBGL_multi_draw", "WEBGL_polygon_mode", ], "wuv": "Google Inc. (Apple)", "wur": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", }, "m": {"compatMode": "CSS1Compat"}, "n": { "vendorSub": "", "productSub": "20030107", "vendor": "Google Inc.", "maxTouchPoints": 0, "doNotTrack": "1", "pdfViewerEnabled": True, "hardwareConcurrency": 10, "cookieEnabled": True, "appCodeName": "Mozilla", "appName": "Netscape", "appVersion": app_version, "platform": "MacIntel", "product": "Gecko", "userAgent": user_agent, "language": "zh-CN", "onLine": True, "webdriver": False, "javaEnabled": False, "deprecatedRunAdAuctionEnforcesKAnonymity": False, "deviceMemory": 8, "enumerationOrder": [ "vendorSub", "productSub", "vendor", "maxTouchPoints", "scheduling", "userActivation", "doNotTrack", "geolocation", "connection", "plugins", "mimeTypes", "pdfViewerEnabled", "webkitTemporaryStorage", "webkitPersistentStorage", "windowControlsOverlay", "hardwareConcurrency", "cookieEnabled", "appCodeName", "appName", "appVersion", "platform", "product", "userAgent", "language", "languages", "onLine", "webdriver", "getGamepads", "javaEnabled", "sendBeacon", "vibrate", "deprecatedRunAdAuctionEnforcesKAnonymity", "protectedAudience", "bluetooth", "storageBuckets", "clipboard", "credentials", "keyboard", "managed", "mediaDevices", "storage", "serviceWorker", "virtualKeyboard", "wakeLock", "deviceMemory", "userAgentData", "login", "ink", "mediaCapabilities", "devicePosture", "hid", "locks", "gpu", "mediaSession", "permissions", "presentation", "serial", "usb", "xr", "adAuctionComponents", "runAdAuction", "canLoadAdAuctionFencedFrame", "canShare", "share", "clearAppBadge", "getBattery", "getUserMedia", "requestMIDIAccess", "requestMediaKeySystemAccess", "setAppBadge", "webkitGetUserMedia", "clearOriginJoinedAdInterestGroups", "createAuctionNonce", "joinAdInterestGroup", "leaveAdInterestGroup", "updateAdInterestGroups", "deprecatedReplaceInURN", "deprecatedURNToURL", "getInstalledRelatedApps", "getInterestGroupAdAuctionData", "registerProtocolHandler", "unregisterProtocolHandler", ], }, "p": [ {"name": "PDF Viewer"}, {"name": "Chrome PDF Viewer"}, {"name": "Chromium PDF Viewer"}, {"name": "Microsoft Edge PDF Viewer"}, {"name": "WebKit built-in PDF"}, ], "w": {"devicePixelRatio": 2, "screenTop": 0, "screenLeft": 0}, "s": { "availHeight": 1080, "availWidth": 1920, "colorDepth": 30, "height": 1080, "width": 1920, "pixelDepth": 30, }, "sc": { "ActiveBorder": "rgb(0, 0, 0)", "ActiveCaption": "rgb(0, 0, 0)", "AppWorkspace": "rgb(255, 255, 255)", "Background": "rgb(255, 255, 255)", "ButtonFace": "rgb(239, 239, 239)", "ButtonHighlight": "rgb(239, 239, 239)", "ButtonShadow": "rgb(239, 239, 239)", "ButtonText": "rgb(0, 0, 0)", "CaptionText": "rgb(0, 0, 0)", "GrayText": "rgb(128, 128, 128)", "Highlight": "rgba(128, 188, 254, 0.6)", "HighlightText": "rgb(0, 0, 0)", "InactiveBorder": "rgb(0, 0, 0)", "InactiveCaption": "rgb(255, 255, 255)", "InactiveCaptionText": "rgb(128, 128, 128)", "InfoBackground": "rgb(255, 255, 255)", "InfoText": "rgb(0, 0, 0)", "Menu": "rgb(255, 255, 255)", "MenuText": "rgb(0, 0, 0)", "Scrollbar": "rgb(255, 255, 255)", "ThreeDDarkShadow": "rgb(0, 0, 0)", "ThreeDFace": "rgb(239, 239, 239)", "ThreeDHighlight": "rgb(0, 0, 0)", "ThreeDLightShadow": "rgb(0, 0, 0)", "ThreeDShadow": "rgb(0, 0, 0)", "Window": "rgb(255, 255, 255)", "WindowFrame": "rgb(0, 0, 0)", "WindowText": "rgb(0, 0, 0)", }, "ss": { "cookie": True, "localStorage": True, "sessionStorage": True, "globalStorage": False, "indexedDB": True, }, "tz": -480, "lil": "", "wil": "", "wi": { "ow": 1920, "oh": 1080, "iw": 397, "ih": 959, "etn": "[object External]", }, } headers = { "accept": "*/*", "accept-language": "zh-CN,zh;q=0.9", "cache-control": "no-cache", "content-type": "application/x-www-form-urlencoded;charset=UTF-8", "origin": "https://shop.m.jd.com", "pragma": "no-cache", "priority": "u=1, i", "referer": "https://shop.m.jd.com/", "sec-ch-ua-mobile": "?1", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "user-agent": user_agent, } data = { "d": self._td_encrypt(d_param), } if not self.__jd_jr_td_risk_pin and self._token: self.__jd_jr_td_risk_pin = self._token a_param = { "pin": self.__jd_jr_td_risk_pin if self.__jd_jr_td_risk_pin else "", "oid": "", "bizId": self.__biz_id, "fc": self._eid if self._eid else "", "mode": "strict", "p": "s", "fp": "6d2c8c60af349b1a87ddd3b065194ca4", "ctype": "1", "v": "4.2.8.0", "pv": "02_mt_5LXK_60653970589", "f": "3", "s": self._get_sign(data["d"] + "_*_UYBN6YGTNO6DHPVB"), "o": self.__o, "qs": urlparse(self.__url).query, "jsTk": "", "qi": "", } data["a"] = self._td_encrypt(a_param) self._request(headers, data)