!QUOTE\] 参考文章 [爬虫逆向之极验四滑块(补环境秒杀)_极验四逆向-CSDN博客](https://blog.csdn.net/m0_64408930/article/details/155533409)
前言
本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
逆向地址
网址 :aHR0cHM6Ly9ndDQuZ2VldGVzdC5jb20v
接口:
captcha_id接口:https://gt4.geetest.com/assets/index.xxxxxxxx.js- 请求验证码接口(load接口):
https://gcaptcha4.geetest.com/load - 验证验证码接口(verify接口):
https://gcaptcha4.geetest.com/verify
逆向目标
通过补环境的方式实现加密参数w的还原
逆向分析
1.三个接口
首先分析一下这三个接口,看看请求和响应的参数是否动态
captcha_id接口 :https://gt4.geetest.com/assets/index.604047e9.js
这个接口返回的是一个js文件,文件里有我们需要的caprcha_id,这个参数需要传递给下面两个接口(请求验证码接口和验证验证码接口)当作请求参数,如下图所示:

请求验证码接口(load接口) :https://gcaptcha4.geetest.com/load
请求参数
callback:geetest_13位的时间戳captcha_id:由captcha_id接口返回challenge:uuid格式的数据client_type:webrisk_type:slide,滑块类型lang:zh
响应数据(JSONP格式),有以下几个参数需要注意:
bg:滑块背景图slice:滑块图片pow_detail:里面的4个参数均作为参数用于生成加密参数wversionbitshashfuncdatetime
lot_number:传递给下一个接口当请求参数,并且传参用于加密参数wpayload:传递给下一个接口当请求参数process_token:传递给下一个接口当请求参数
验证验证码接口(verify接口) :https://gcaptcha4.geetest.com/verify
请求参数
callback:geetest_13位的时间戳captcha_id:由captcha_id接口返回client_type:webrisk_type:slide,滑块类型lot_number:由请求验证码接口返回payload:由请求验证码接口返回process_token:由请求验证码接口返回payload_protocol:1pt:1,作为参数用于生成加密参数ww:加密参数(1536位),接下去要分析的重点
2.加密参数w
接下去分析加密参数w,接着就是跟栈找加密参数的入口,在第一处地方打上断点,然后再次拖动滑块(不要成功),断点断住了,如下图所示:

接着往下跟栈,直到找到加密参数生成的位置,最终找到了加密的位置,如下图所示:

接着就是分析加密参数是由那个函数生成的,可以发现加密函数就在上面,如下图所示:

接着跟进函数,查看具体的加密逻辑,进去后在两个return处打上断点,点击执行,跳到了第二个断点,如下图所示:

然后查看返回的数据,看能不能对上,发现是1504位,能对应上,如下图所示:

既然能对应上,就可以把这个i函数导出到全局,接着复制所有代码到本地,然后补环境,在本地实现加密参数的生成,如下图所示:

然后发现报错了,如下图所示:

第2个参数
根据报错信息,发现是一个pt值没有,所以继续去浏览器中调试,找到响应的位置,如下图所示:


第1个参数
从上面的两个图中,可以看出少了入参里面的options参数的pt参数,所以在代码中补充完整,接着w参数就出来了,如下图所示:

经过对比,虽然本地生成的加密参数长度也是1504位,但是每次出的值却是不一样的,应该是加密的过程中有随机数,之后再看吧!!!
接着就是分析入参的第一个参数了,为了方便,把参数进行格式化,之后调用加密函数的时候再转成Json字符串,如下图所示:

分析一下几个比较关键的参数:
01.setLeft
这个参数需要获取滑块的两张图片,然后使用opencv 获取缺口的位置
02.userresponse
-
从后往前找生成的地方,找到了这里:

-
说明生成的地方还在前面,继续往前,也可以直接搜索
userresponse,发现有12个地方,在每处都打上断点,然后刷新页面,滑动滑块,看最后断在哪里,如下图所示:
-
仔细观察发现
userresponse的计算公式为:x坐标(setLeft) / 1.0059466666666665 + 2
03.pow_msg
也是直接搜索pow_msg,发现有三处,刷新页面后断住了,如下图所示:

接着就是找c和h,发现c的参数都是上一个堆栈init传过来的,于是往上跟栈,如下图所示:

上面图片的6个参数就是组成c的参数,接下去要去看生成的逻辑
-
第1个参数
_ᖁᖁᕷᖗ,是lot_number,lot_number又是由load接口返回的,如下图所示:
-
第2个参数
_ᖁᖘᕷᖘ,它是captch_id,由captcha_id接口返回,如下图所示:
-
剩下的4个参数,也都是由
load接口返回的数据
接着继续回去看参数h,发现是由guid这个函数生成的,如下图所示:

跟进去看看逻辑,发现参数是由e函数生成的,如下图所示:

e函数里面的逻辑:生成一个 4 位随机的小写十六进制字符串,如下图所示:
- 等价逻辑代码:
Math.random().toString(16).substr(2, 4)

因为这个值是随机的,全程都需要保持一致,所以也需要把整个函数导出到全局,方便调用,如下图所示:

04.pow_sign
接下去看pow_sign这个参数,是由l赋值而来的,并且是由pow_msg加密得来的,加密的方式也有三种:md5,sha1,sha256,所以其实也可以把pow_msg里的加密方式写成活的,不要固定住。

这里也有需要要注意的,就是pow_sign最后的结果需要以'00'开头 ,否则使用python程序验证接口的时候,data里的result会是forbidden。如下图所示:

如何让pow_sign最后的结果以是'00'开头,只需要不停的生成h,如下图所示:

05.XwNA
这个参数其实固定住也可以,生成逻辑就是下面这张图:

06.接下去分析这4个值,如下图所示:

上面图片中的三个值都是lot_number中截取出来的值,
- str1:
6ec3db====> 索引19开始截取6位 - str2:
dbf78654====> 索引23开始截取8位 - str3:
07836054====> 索引5开始截取8位 - str4:
1090b6====> 索引14开始截取6位
代码中的逻辑如下,规律如下图所示:

参数分析的差不多了,看看结果:

结果演示

最终代码
JS代码
javascript
const CryptoJs = require("crypto-js");
// delete __dirname;
// delete __filename;
// ========================= 工具函数 =========================
dtavm = {};
dtavm.log = console.log;
function proxy(obj, objname, type) {
function getMethodHandler(WatchName, target_obj) {
let methodhandler = {
apply(target, thisArg, argArray) {
if (this.target_obj) {
thisArg = this.target_obj;
}
let result = Reflect.apply(target, thisArg, argArray);
if (target.name !== "toString") {
if (target.name === "addEventListener") {
dtavm.log(
`调用者 => [${WatchName}] 函数名 => [${target.name}], 传参 => [${argArray[0]}], 结果 => [${result}].`,
);
} else if (WatchName === "window.console") {
} else {
dtavm.log(
`调用者 => [${WatchName}] 函数名 => [${target.name}], 传参 => [${argArray}], 结果 => [${result}].`,
);
}
} else {
dtavm.log(
`调用者 => [${WatchName}] 函数名 => [${target.name}], 传参 => [${argArray}], 结果 => [${result}].`,
);
}
return result;
},
construct(target, argArray, newTarget) {
var result = Reflect.construct(target, argArray, newTarget);
dtavm.log(
`调用者 => [${WatchName}] 构造函数名 => [${target.name}], 传参 => [${argArray}], 结果 => [${result}].`,
);
return result;
},
};
methodhandler.target_obj = target_obj;
return methodhandler;
}
function getObjhandler(WatchName) {
let handler = {
get(target, propKey, receiver) {
let result = target[propKey];
if (result instanceof Object) {
if (typeof result === "function") {
dtavm.log(
`调用者 => [${WatchName}] 获取属性名 => [${propKey}] , 是个函数`,
);
return new Proxy(result, getMethodHandler(WatchName, target));
} else {
dtavm.log(
`调用者 => [${WatchName}] 获取属性名 => [${propKey}], 结果 => [${result}]`,
);
}
return new Proxy(result, getObjhandler(`${WatchName}.${propKey}`));
}
if (typeof propKey !== "symbol") {
dtavm.log(
`调用者 => [${WatchName}] 获取属性名 => [${propKey?.description ?? propKey}], 结果 => [${result}]`,
);
}
return result;
},
set(target, propKey, value, receiver) {
if (value instanceof Object) {
dtavm.log(
`调用者 => [${WatchName}] 设置属性名 => [${propKey}], 值为 => [${value}]`,
);
} else {
dtavm.log(
`调用者 => [${WatchName}] 设置属性名 => [${propKey}], 值为 => [${value}]`,
);
}
return Reflect.set(target, propKey, value, receiver);
},
has(target, propKey) {
var result = Reflect.has(target, propKey);
dtavm.log(
`针对in操作符的代理has=> [${WatchName}] 有无属性名 => [${propKey}], 结果 => [${result}]`,
);
return result;
},
deleteProperty(target, propKey) {
var result = Reflect.deleteProperty(target, propKey);
dtavm.log(
`拦截属性delete => [${WatchName}] 删除属性名 => [${propKey}], 结果 => [${result}]`,
);
return result;
},
defineProperty(target, propKey, attributes) {
var result = Reflect.defineProperty(target, propKey, attributes);
dtavm.log(
`拦截对象define操作 => [${WatchName}] 待检索属性名 => [${propKey.toString()}] 属性描述 => [${attributes}], 结果 => [${result}]`,
);
// debugger
return result;
},
getPrototypeOf(target) {
var result = Reflect.getPrototypeOf(target);
dtavm.log(`被代理的目标对象 => [${WatchName}] 代理结果 => [${result}]`);
return result;
},
setPrototypeOf(target, proto) {
dtavm.log(
`被拦截的目标对象 => [${WatchName}] 对象新原型==> [${proto}]`,
);
return Reflect.setPrototypeOf(target, proto);
},
preventExtensions(target) {
dtavm.log(`方法用于设置preventExtensions => [${WatchName}] 防止扩展`);
return Reflect.preventExtensions(target);
},
isExtensible(target) {
var result = Reflect.isExtensible(target);
dtavm.log(
`拦截对对象的isExtensible() => [${WatchName}] isExtensible, 返回值==> [${result}]`,
);
return result;
},
};
return handler;
}
if (type === "method") {
return new Proxy(obj, getMethodHandler(objname, obj));
}
return new Proxy(obj, getObjhandler(objname));
}
// 【全局防 toString 检测 · 完整版】
(() => {
"use strict";
// 1. 保存原生的 Function.toString 方法
const $toString = Function.toString;
// 2. 创建唯一隐形标记(Symbol)
const myFunction_toString_symbol = Symbol(
"_" + Math.random().toString(36).slice(2),
);
// 3. 自定义 toString 逻辑(检票员)
const myToString = function () {
return (
(typeof this === "function" && this[myFunction_toString_symbol]) ||
$toString.call(this)
);
};
// 4. 隐形贴标工具(给函数挂隐藏属性)
function set_native(func, key, value) {
Object.defineProperty(func, key, {
enumerable: false, // 设置为【不可枚举】= 隐形
configurable: true,
writable: true,
value: value, // 要伪装的内容:原生函数字符串{[native code]}
});
}
// 5. 替换全局的 toString(接管所有函数)
delete Function.prototype.toString; // 删除系统原生的toString
set_native(Function.prototype, "toString", myToString);
// Function.prototype:安装目标 → 函数总原型
// 'toString':安装的属性名 → 覆盖 toString 方法
// myToString:我们自己写的自定义 toString 逻辑(有标记就伪装原生,没标记就正常输出)
// 6. 给全局挂载一键伪装工具
globalThis.safefunction = (func) => {
set_native(
func, // 要伪装的函数
myFunction_toString_symbol, // 要伪装的属性名
`function ${func.name || ""}() { [native code] }`, // 要伪装的内容
);
};
// 7. 自我保护:让我们的伪装工具也看起来像原生
set_native(
Function.prototype.toString,
myFunction_toString_symbol,
"function toString() { [native code] }",
);
})();
function obj_toString(obj, tag) {
// 标准原生属性配置(完全模拟浏览器原生行为,防检测)
Object.defineProperty(obj, Symbol.toStringTag, {
value: tag,
enumerable: false, // 不可枚举(原生对象默认行为)
configurable: true, // 可重新配置(防止重复定义报错)
writable: false, // 不可修改(更贴近原生,防二次检测)
});
}
// ========================= 补环境代码 =========================
// EventTarget
EventTarget = function EventTarget() {
};
safefunction(EventTarget);
EventTarget.prototype.addEventListener = function () {
};
safefunction(EventTarget.prototype.addEventListener);
// Node
Node = function Node() {
};
safefunction(Node);
Object.setPrototypeOf(Node.prototype, EventTarget.prototype);
// window
Window = function Window() {
};
safefunction(Window);
Object.setPrototypeOf(Window.prototype, EventTarget.prototype);
window = global;
self = top = globalThis = parent = window;
Object.setPrototypeOf(window, Window.prototype);
obj_toString(window, "Window");
// window = proxy(window, "window");
// HTMLHtmlElement
HTMLHtmlElement = function HTMLHtmlElement() {
};
safefunction(HTMLHtmlElement);
// Object.setPrototypeOf(HTMLHtmlElement.prototype, HTMLElement.prototype);
html = new HTMLHtmlElement();
obj_toString(html, "HTMLHtmlElement");
// html = proxy(html, "html");
// HTMLHeadElement
HTMLHeadElement = function HTMLHeadElement() {
};
safefunction(HTMLHeadElement);
// Object.setPrototypeOf(HTMLHeadElement.prototype, HTMLElement.prototype);
head = new HTMLHeadElement();
obj_toString(head, "HTMLHeadElement");
// head = proxy(head, "head");
// HTMLBodyElement
HTMLBodyElement = function HTMLBodyElement() {
};
safefunction(HTMLBodyElement);
// Object.setPrototypeOf(HTMLBodyElement.prototype, HTMLElement.prototype);
HTMLBodyElement.prototype.removeChild = function (args) {
console.log("对象:HTMLHtmlElement.removeChild", args);
};
body = new HTMLBodyElement();
obj_toString(body, "HTMLBodyElement");
// body = proxy(body, "body");
// document
Document = function Document() {
};
safefunction(Document);
Object.setPrototypeOf(Document.prototype, Node.prototype);
Document.prototype.head = head;
Document.prototype.body = body;
Document.prototype.documentElement = html;
Document.prototype.createElement = function createElement(tag) {
console.log(`特殊检测: createElement 被调用 (tag: ${tag})`);
};
safefunction(Document.prototype.createElement);
HTMLDocument = function HTMLDocument() {
};
safefunction(HTMLDocument);
Object.setPrototypeOf(HTMLDocument.prototype, Document.prototype);
document = new HTMLDocument();
obj_toString(document, "HTMLDocument");
// document = proxy(document, "document");
// navigator
Navigator = function Navigator() {
};
safefunction(Navigator);
Navigator.prototype = {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36'
,
platform: "Win32",
hardwareConcurrency: 16,
webdriver: false,
languages: ["zh-CN", "zh"],
appName: "Netscape",
appVersion: '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36',
plugins: {
0: {
0: {},
1: {},
},
1: {
0: {},
1: {},
},
2: {
0: {},
1: {},
},
3: {
0: {},
1: {},
},
4: {
0: {},
1: {},
},
length: 5,
},
};
navigator = new Navigator();
obj_toString(navigator, "Navigator");
// navigator = proxy(navigator, "navigator");
// location
Location = function Location() {
};
safefunction(Location);
location = new Location();
location = {
ancestorOrigins: {},
href: "https://gt4.geetest.com/",
origin: "https://gt4.geetest.com",
protocol: "https:",
host: "gt4.geetest.com",
hostname: "gt4.geetest.com",
port: "",
pathname: "/",
search: "",
hash: "",
};
obj_toString(location, "Location");
// location = proxy(location, "location");
// Screen
Screen = function Screen() {
};
safefunction(Screen);
screen = new Screen();
obj_toString(screen, "Screen");
// screen = proxy(screen, "screen");
// localStorage
Storage = function Storage() {
};
safefunction(Storage);
localStorage = new Storage();
obj_toString(localStorage, "Storage");
// localStorage = proxy(localStorage, "localStorage");
// crypto
Crypto = function Crypto() {
};
safefunction(Crypto);
Crypto.prototype.getRandomValues = function getRandomValues(typedArray) {
for (let i = 0; i < typedArray.length; i++) {
// 生成对应类型的随机数
if (typedArray instanceof Uint8Array) {
typedArray[i] = Math.floor(Math.random() * 256); // 0-255
} else if (typedArray instanceof Uint16Array) {
typedArray[i] = Math.floor(Math.random() * 65536); // 0-65535
} else if (typedArray instanceof Uint32Array) {
typedArray[i] = Math.floor(Math.random() * 4294967296); // 0-4294967295
}
}
// ✅ 关键:必须返回传入的数组(原生规范)
return typedArray;
};
safefunction(Crypto.prototype.getRandomValues);
crypto = new Crypto();
obj_toString(crypto, "Crypto");
// crypto = proxy(crypto, "crypto");
// ========================= 源码 =========================
require("./source.js");
// ========================= 测试 =========================
function get_w(x, hashfunc, lot_number, datetime, captcha_id) {
let key;
let pow_msg;
let pow_sign;
// 🔥 核心:循环生成 key,直到 pow_sign 以 00 开头
do {
// 每次循环重新生成 key
key = window.key();
// 拼接 pow_msg
pow_msg = `1|8|${hashfunc}|${datetime}|${captcha_id}|${lot_number}||${key}`;
// 计算 sha256
if (hashfunc === 'md5') {
pow_sign = CryptoJs.MD5(pow_msg).toString();
} else if (hashfunc === 'sha1') {
pow_sign = CryptoJs.SHA1(pow_msg).toString();
} else if (hashfunc === 'sha256') {
pow_sign = CryptoJs.SHA256(pow_msg).toString();
}
// 调试:打印看看是否满足条件
// console.log("重试中...pow_sign:", pow_sign);
} while (pow_sign.indexOf('00') !== 0); // 不满足就一直循环
console.log("✅ 找到符合条件的pow_sign:", pow_sign);
console.log("✅ 对应key:", key);
str1 = lot_number.substr(19, 6) // 索引19开始截取6位
str2 = lot_number.substr(23, 8) // 索引23开始截取8位
str3 = lot_number.substr(5, 8) // 索引5开始截取8位
str4 = lot_number.substr(14, 6) // 索引14开始截取6位
const passtime = Math.floor(Math.random() * 300) + 500; // 300-800ms 真人耗时
aaa = {
"setLeft": x, // x坐标
"passtime": passtime, // 滑动耗时
"userresponse": x / 1.0059466666666665 + 2, // x坐标(setLeft) / 1.0059466666666665 + 2
"device_id": "",
"lot_number": lot_number, // load接口返回
// hashfunc、datetime 由load接口返回, captch_id 由captcha_id接口返回
"pow_msg": pow_msg,
// 由pow_msg 加密得来
"pow_sign": pow_sign,
"geetest": "captcha", // 固定的
"lang": "zh", // 固定的
"ep": "123", // 固定的
"biht": "1426265548", // 固定的
"gee_guard": {
"roe": {
"aup": "3",
"sep": "3",
"egp": "3",
"auh": "3",
"rew": "3",
"snh": "3",
"res": "3",
"cdc": "3"
}
},
"4MTT": "0Qh0", // 动态的
// 3个值都来自load接口,截取lot_number中的部分值
[str1]: {
[str2]: {
[str3]: str4
}
},
"em": { "ph": 0, "cp": 0, "ek": "11", "wd": 1, "nt": 0, "si": 0, "sc": 0 } // 固定的
};
console.log(aaa)
arr = {
options: { 'pt': '1' }
}
result = window.iii(JSON.stringify(aaa), arr);
return result;
}
// console.log(get_w(196, 'sha256', "dbbcf007750e48bea942e4bb7d350bc9", '2026-04-03T13:29:07.115836+08:00', '54088bb07d2df3c46b79f80300b0abbe'));
Python代码
python
import requests
import json
import time
import re
import execjs
import cv2
import random
import numpy as np
jscode = open("滑动拼图验证码.js", 'r', encoding="utf-8").read()
session = requests.Session()
headers = {
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Referer": "https://gt4.geetest.com/",
"Sec-Fetch-Dest": "script",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-site",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
"sec-ch-ua": "\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Google Chrome\";v=\"146\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\""
}
cookies = {
"captcha_v4_user": "",
"Hm_lvt_25b04a5e7a64668b9b88e2711fb5f0c4": "1775014501",
"sensorsdata2015jssdkcross": ""
}
session.headers.clear()
session.headers.update(headers)
session.cookies.update(cookies)
# 动态生成challenge,uuid格式
def generate_challenge():
def replace(c):
r = random.randint(0, 15)
v = r if c == 'x' else (r & 0x3 | 0x8)
return hex(v)[2:]
uuid_str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
result = ''
for char in uuid_str:
result += replace(char) if char in ('x','y') else char
return result
# 解析JSONP响应
def parse_jsonp(response_text):
"""把 geetest_xxx({...}) 转为 纯JSON"""
left = response_text.find('(') + 1
right = response_text.rfind(')')
json_str = response_text[left:right]
return json.loads(json_str)
# 把背景图、滑块图转成字节数据
def get_captcha_image(load_response):
bg_url = "https://static.geetest.com/" + load_response['data']['bg']
tp_url = "https://static.geetest.com/" + load_response['data']['slice']
# 直接获取图片字节数据,不保存本地
bg_bytes = session.get(bg_url).content
slice_bytes = session.get(tp_url).content
return bg_bytes, slice_bytes
# 识别缺口
def detect_gap(bg_bytes: bytes, slice_bytes: bytes) -> int:
# 1. 解码图片
bg_arr = np.frombuffer(bg_bytes, np.uint8)
sl_arr = np.frombuffer(slice_bytes, np.uint8)
bg_img = cv2.imdecode(bg_arr, cv2.IMREAD_COLOR)
sl_img = cv2.imdecode(sl_arr, cv2.IMREAD_UNCHANGED)
# 2. 从滑块 alpha 通道提取拼图轮廓
alpha = sl_img[:, :, 3] # 取 alpha 通道
alpha_bin = (alpha > 128).astype(np.uint8) * 255 # 二值化
alpha_edge = cv2.Canny(alpha_bin, 100, 200) # Canny 边缘检测
# 3. 对背景图做边缘检测
bg_gray = cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY)
bg_edge = cv2.Canny(bg_gray, 100, 200)
# 4. 模板匹配 - 拼图轮廓 vs 背景边缘
result = cv2.matchTemplate(bg_edge, alpha_edge, cv2.TM_CCOEFF_NORMED)
_, _, _, max_loc = cv2.minMaxLoc(result)
return max_loc[0] # 缺口 x 坐标
# 请求 captcha_id 接口
def get_captcha_id():
# 请求的js文件名是会变化的,有效时间大概6-8个小时
url = "https://gt4.geetest.com/assets/index.1395bf87.js"
response = session.get(url)
pattern = r'captchaId:"([a-f0-9]{32})"'
match = re.search(pattern, response.text)
captcha_id = match.group(1)
return captcha_id
# 请求验证码接口(load接口)
def get_captcha_load(captcha_id):
# 动态生成callback(时间戳,极验要求)
callback = f"geetest_{int(time.time() * 1000)}"
# 动态生成 challenge
challenge = generate_challenge()
params = {
"callback": callback,
"captcha_id": captcha_id,
"challenge": challenge,
"client_type": "web", # 验证码类型
"risk_type": "slide",
"lang": "zh",
}
url = "https://gcaptcha4.geetest.com/load"
response = session.get(url, params=params)
result = parse_jsonp(response.text)
return result
# 请求验证接口(verify接口)
def get_captcha_verify(captcha_id, load_response, w):
# 动态生成callback(时间戳,极验要求)
callback = f"geetest_{int(time.time() * 1000)}"
# 从load结果中拿【最新、未过期】的参数
lot_number = load_response["data"]["lot_number"]
payload = load_response["data"]["payload"]
process_token = load_response["data"]["process_token"]
params = {
# 时间戳,动态
"callback": callback,
"captcha_id": captcha_id, # 验证码id
"client_type": "web",
# 动态,由load接口返回
"lot_number": lot_number,
"risk_type": "slide",
# 动态,由load接口返回
"payload": payload,
# 动态,由load接口返回
"process_token": process_token,
"payload_protocol": "1",
"pt": "1",
# 加密参数,1536位
"w": w,
}
# print("verify接口请求参数>>>>",params)
url = "https://gcaptcha4.geetest.com/verify"
response = session.get(url, params=params)
result = parse_jsonp(response.text)
return result
# 封装核心验证流程
def run_captcha(load_response, captcha_id):
# 1. 获取图片字节流
bg_bytes, slice_bytes = get_captcha_image(load_response)
# 2. 调用你想用的 detect_gap 计算滑动距离
setLeft = detect_gap(bg_bytes, slice_bytes)
print(f"✅ 正确缺口距离>>>> {setLeft}")
hashfunc = load_response['data']['pow_detail']['hashfunc']
lot_number = load_response["data"]["lot_number"]
datetime = load_response['data']["pow_detail"]["datetime"]
w = execjs.compile(jscode).call('get_w', setLeft, hashfunc, lot_number, datetime, captcha_id)
# print(f"✅ 加密参数w>>>> {w[:100]}...")
verify_response = get_captcha_verify(captcha_id, load_response, w)
return verify_response
if __name__ == '__main__':
captcha_id = get_captcha_id()
# print("captcha_id>>>>", captcha_id)
# 首次获取Load数据
load_response = get_captcha_load(captcha_id)
# print("load接口响应数据>>>>", load_response)
# 执行验证流程
verify_response = run_captcha(load_response, captcha_id)
print("verify接口返回响应>>>>>", verify_response)
# 判断验证结果
if verify_response['data']['result'] == 'success':
print("✅ 验证成功!")
else:
print("❌ 验证失败!")
如有错误请各位大佬指出,感谢