极验4消消乐验证码逆向笔记

前言

本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

逆向地址

网址aHR0cHM6Ly9ndDQuZ2VldGVzdC5jb20v
接口

  1. captcha_id接口:https://gt4.geetest.com/assets/index.xxxxxxxx.js
  2. 请求验证码接口(load接口):https://gcaptcha4.geetest.com/load
  3. 验证验证码接口(verify接口):https://gcaptcha4.geetest.com/verify

逆向分析

1.三个接口

首先分析一下这三个接口,看看请求和响应的参数是否动态

captcha_id接口https://gt4.geetest.com/assets/index.604047e9.js

这个接口返回的是一个js文件,文件里有我们需要的caprcha_id,这个参数需要传递给下面两个接口(请求验证码接口和验证验证码接口)当作请求参数,如下图所示:

请求验证码接口(load接口)https://gcaptcha4.geetest.com/load

请求参数

  1. callback:geetest_13位的时间戳
  2. captcha_id:由captcha_id接口返回
  3. challenge:uuid格式的数据
  4. client_type:web
  5. risk_type:match,消消乐验证码
  6. lang:zho

响应数据(JSONP格式),有以下几个参数需要注意:

  1. ques:消消乐矩阵(3x3),每个数字代表一种图片,将三个相同数字换到同一列或同一行即可通过
  2. pow_detail:里面的4个参数均作为参数用于生成加密参数w
    • version
    • bits
    • hashfunc
    • datetime
  3. lot_number:传递给下一个接口当请求参数,并且传参用于加密参数w
  4. payload:传递给下一个接口当请求参数
  5. process_token:传递给下一个接口当请求参数

验证验证码接口(verify接口)https://gcaptcha4.geetest.com/verify

请求参数

  1. callback:geetest_13位的时间戳
  2. captcha_id:由captcha_id接口返回
  3. client_type:web
  4. risk_type:match,消消乐验证码
  5. lot_number:由请求验证码接口返回
  6. payload:由请求验证码接口返回
  7. process_token:由请求验证码接口返回
  8. payload_protocol:1
  9. pt:1,作为参数用于生成加密参数w
  10. w:加密参数(1504位),接下去要分析的重点

2.加密参数w

加密参数w就不分析了,和上一篇文章的一样,只不过传入的参数从移动的距离setLeft变成了点击两块拼图的坐标userresponse

点击传送到上一篇文章

3.加密参数userresponse

1.验证码规则

原始数据是一个3 行 3 列的矩阵,我们的目标:

找到唯一一对需要交换的元素,交换后让一行 / 一列全部相同。

2.核心规律

所有消消乐题目,都满足一个固定特征:

存在一行 / 一列:2 个数字相同,1 个数字不同

我们只需要找到这组数据,再判断:

  • 不同数字的上方 / 下方(列场景)

  • 不同数字的左侧 / 右侧(行场景)

    是否是目标数字,即可确定交换坐标。

3.代码

python 复制代码
# 处理消消乐验证码,返回拼图前后的数据
def solve_match_puzzle(ques):
    # 1. 转置矩阵:处理【原始列】(极验核心逻辑)
    grid = [list(item) for item in zip(*ques)]

    # ========== 第一部分:遍历转置后的行 = 原始数据的列 ==========
    for r, row in enumerate(grid):
        # 过滤无重复的行
        same_nums = [n for n in row if row.count(n) == 2]
        if not same_nums:
            continue
        target = same_nums[0]
        # 找到唯一不同元素的列索引
        diff_nums = [n for n in row if row.count(n) == 1]
        if not diff_nums:
            continue
        c = row.index(diff_nums[0])

        coord1 = [r, c]
        coord2 = None
        # 判断上下交换
        if r == 0 and grid[r + 1][c] == target:
            coord2 = [r + 1, c]
        elif r == 2 and grid[r - 1][c] == target:
            coord2 = [r - 1, c]
        elif r == 1:
            if grid[r - 1][c] == target:
                coord2 = [r - 1, c]
            elif grid[r + 1][c] == target:
                coord2 = [r + 1, c]
        # 找到答案,转换为原始坐标并返回
        if coord2:
            original_coord1 = [coord1[1], coord1[0]]
            original_coord2 = [coord2[1], coord2[0]]
            return [original_coord1, original_coord2]

    # ========== 第二部分:遍历原始数据的行(完整补充) ==========
    for r, row in enumerate(ques):
        same_nums = [n for n in row if row.count(n) == 2]
        if not same_nums:
            continue
        target = same_nums[0]
        diff_nums = [n for n in row if row.count(n) == 1]
        if not diff_nums:
            continue
        c = row.index(diff_nums[0])

        coord1 = [r, c]
        coord2 = None
        if r == 0 and ques[r + 1][c] == target:
            coord2 = [r + 1, c]
        elif r == 2 and ques[r - 1][c] == target:
            coord2 = [r - 1, c]
        elif r == 1:
            if ques[r - 1][c] == target:
                coord2 = [r - 1, c]
            elif ques[r + 1][c] == target:
                coord2 = [r + 1, c]
        if coord2:
            return [coord1, coord2]

    # 兜底
    return None

结果演示

最终代码

JS代码

javascript 复制代码
const CryptoJs = require("crypto-js");

// ========================= 工具函数 =========================

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(userresponse,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 = {
        "passtime": passtime, // 滑动耗时
        "userresponse": userresponse,  // [[第1个点击的坐标],[第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([[2,2],[1,2]], 'sha256', "dbbcf007750e48bea942e4bb7d350bc9", '2026-04-03T13:29:07.115836+08:00', '54088bb07d2df3c46b79f80300b0abbe'));

Python代码

python 复制代码
import requests
import execjs
import time
import re
import random
import json

jsocde = open('消消乐验证码.js','r',encoding='utf-8').read()
jscompile = execjs.compile(jsocde)


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": "",
    "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 solve_match_puzzle(ques):
    # 1. 转置矩阵:处理【原始列】(极验核心逻辑)
    grid = [list(item) for item in zip(*ques)]

    # ========== 第一部分:遍历转置后的行 = 原始数据的列 ==========
    for r, row in enumerate(grid):
        # 过滤无重复的行
        same_nums = [n for n in row if row.count(n) == 2]
        if not same_nums:
            continue
        target = same_nums[0]
        # 找到唯一不同元素的列索引
        diff_nums = [n for n in row if row.count(n) == 1]
        if not diff_nums:
            continue
        c = row.index(diff_nums[0])

        coord1 = [r, c]
        coord2 = None
        # 判断上下交换
        if r == 0 and grid[r + 1][c] == target:
            coord2 = [r + 1, c]
        elif r == 2 and grid[r - 1][c] == target:
            coord2 = [r - 1, c]
        elif r == 1:
            if grid[r - 1][c] == target:
                coord2 = [r - 1, c]
            elif grid[r + 1][c] == target:
                coord2 = [r + 1, c]
        # 找到答案,转换为原始坐标并返回
        if coord2:
            original_coord1 = [coord1[1], coord1[0]]
            original_coord2 = [coord2[1], coord2[0]]
            return [original_coord1, original_coord2]

    # ========== 第二部分:遍历原始数据的行(完整补充) ==========
    for r, row in enumerate(ques):
        same_nums = [n for n in row if row.count(n) == 2]
        if not same_nums:
            continue
        target = same_nums[0]
        diff_nums = [n for n in row if row.count(n) == 1]
        if not diff_nums:
            continue
        c = row.index(diff_nums[0])

        coord1 = [r, c]
        coord2 = None
        if r == 0 and ques[r + 1][c] == target:
            coord2 = [r + 1, c]
        elif r == 2 and ques[r - 1][c] == target:
            coord2 = [r - 1, c]
        elif r == 1:
            if ques[r - 1][c] == target:
                coord2 = [r - 1, c]
            elif ques[r + 1][c] == target:
                coord2 = [r + 1, c]
        if coord2:
            return [coord1, coord2]

    # 兜底
    return None




# 请求 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": "match",
        "lang": "zho"
    }
    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",
        # 加密参数,1504位
        "w": w,
    }
    # print("verify接口请求参数>>>>",params)
    url = "https://gcaptcha4.geetest.com/verify"
    response = session.get(url, params=params)

    result = parse_jsonp(response.text)
    return result


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)

    lot_number = load_response["data"]["lot_number"]
    hashfunc = load_response["data"]["pow_detail"]["hashfunc"]
    datetime = load_response["data"]["pow_detail"]["datetime"]

    # 处理消消乐验证码,返回拼图前后的数据
    ques = load_response["data"]["ques"]
    userresponse = solve_match_puzzle(ques)
    # userresponse = input("请输入拼图前/后数据:")
    print("拼图前/后数据>>>>", userresponse)

    w = jscompile.call("get_w",userresponse, hashfunc, lot_number, datetime, captcha_id)
    print("加密参数w>>>>", w)

    # 执行验证流程
    verify_response = get_captcha_verify(captcha_id, load_response,w)
    print("验证结果>>>>", verify_response)
相关推荐
单片机学习之路1 小时前
【Python】输入print函数
开发语言·前端·python
李昊哲小课2 小时前
Python办公自动化教程 - 第1章 openpyxl基础入门 - 第一次用代码操控Excel
开发语言·python·excel·openpyxl
智算菩萨2 小时前
【Python图像处理】4 NumPy数组操作与图像矩阵运算
图像处理·python·numpy
SomeB1oody2 小时前
【Python深度学习】1.1. 多层感知器MLP(人工神经网络)介绍
开发语言·人工智能·python·深度学习·机器学习
数据科学小丫2 小时前
数据分析利器 Pandas :apply() 方法 + map() 配对 + 计算描述统计 + 协方差和相关性 + 异常值处理常用方法(基于 python )
python·数据分析·numpy·pandas
财经资讯数据_灵砚智能2 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年4月6日
大数据·人工智能·python·信息可视化·语言模型·自然语言处理·ai编程
圣光SG2 小时前
项目分析与程序设计 学习笔记
笔记·学习·学习笔记·程序设计·项目分析
爱写代码的小朋友2 小时前
使用 Nuitka 打包 Python 应用:从入门到进阶
开发语言·python
不屈的铝合金2 小时前
Python入门:数字类型与运算
python·数据类型·python类型判断与转换·python运算符优先级