Tutorials

浏览器自动化框架中的 GeeTest token 提交

自动化脚本拿到 CaptchaAI 的 GeeTest(极验)v3 识别结果后,还是收到“验证失败”——问题往往不在识别本身,而是三个返回值没写对位置。GeeTest v3 会返回 geetest_challengegeetest_validategeetest_seccode,站点可能用隐藏字段、回调或 XHR 接收它们,下面按 Playwright、Puppeteer、Selenium 逐一说明。

背景: GeeTest(极验)是国内厂商产品,国内站点比海外站点更常用它。

需要提交的三个值

CaptchaAI 返回一个三段式结果:

{
  "geetest_challenge": "a1b2c3d4e5...modified_challenge",
  "geetest_validate": "abc123def456_validate",
  "geetest_seccode": "abc123def456_validate|jordan"
}

三个值分工不同,缺一不可:

  • geetest_challenge:标识会话
  • geetest_validate:证明挑战已通过
  • geetest_seccode:防篡改哈希值

第 1 步:提取参数并完成识别

所有框架的第一步相同——先拿到 gtchallenge,再调用 CaptchaAI 完成识别:

import requests
import time

def solve_geetest(gt, challenge, page_url):
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": "YOUR_API_KEY",
        "method": "geetest",
        "gt": gt,
        "challenge": challenge,
        "pageurl": page_url,
        "json": 1
    })
    task_id = resp.json()["request"]

    for _ in range(60):
        time.sleep(3)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": "YOUR_API_KEY",
            "action": "get",
            "id": task_id,
            "json": 1
        })
        data = result.json()
        if data["status"] == 1:
            return data["request"]  # Returns dict with three values
    raise TimeoutError("GeeTest solve timed out")

data["request"] 返回的是包含三个字段的字典,下一步就是把它写入页面会话。

第 2 步:在 Playwright 中提交结果(Python)

提取参数

先启动浏览器实例,再拿到 gtchallenge

  • 打开目标页面
  • 监听 register 响应,取出 gtchallenge
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()

    # Capture GeeTest registration response
    geetest_data = {}
    def capture_geetest(response):
        if "register" in response.url and response.status == 200:
            try:
                data = response.json()
                if "gt" in data and "challenge" in data:
                    geetest_data.update(data)
            except Exception:
                pass

    page.on("response", capture_geetest)
    page.goto("https://staging.example.com/qa-login")
    page.wait_for_selector(".geetest_holder")

    gt = geetest_data["gt"]
    challenge = geetest_data["challenge"]

写入验证结果

调用 CaptchaAI 完成识别后,把三个值写入隐藏表单字段:

# Solve with CaptchaAI
solution = solve_geetest(gt, challenge, page.url)

# Method 1: Set hidden form fields
page.evaluate(f"""
    const fields = {{
        'geetest_challenge': '{solution["geetest_challenge"]}',
        'geetest_validate': '{solution["geetest_validate"]}',
        'geetest_seccode': '{solution["geetest_seccode"]}'
    }};
    for (const [name, value] of Object.entries(fields)) {{
        let input = document.querySelector(`input[name="${{name}}"]`);
        if (!input) {{
            input = document.createElement('input');
            input.type = 'hidden';
            input.name = name;
            document.querySelector('form').appendChild(input);
        }}
        input.value = value;
    }}
""")

# Submit the form
page.click("#submit-button")

方法二:触发 GeeTest 回调事件

提示: 两种方法二选一,多数站点用方法一就够了。

部分站点用 JavaScript 回调接收结果,而不是表单字段:

page.evaluate(f"""
    // Find the GeeTest captcha object
    if (window.captchaObj) {{
        // Simulate a successful solve
        const result = {{
            geetest_challenge: '{solution["geetest_challenge"]}',
            geetest_validate: '{solution["geetest_validate"]}',
            geetest_seccode: '{solution["geetest_seccode"]}'
        }};

        // Override getValidate to return our solution
        window.captchaObj.getValidate = function() {{ return result; }};

        // Trigger the success callback
        const successEvent = new Event('geetest_success');
        document.dispatchEvent(successEvent);
    }}
""")

第 3 步:在 Puppeteer 中提交结果(JavaScript)

思路和 Playwright 一致,只是 Puppeteer 的 API 写法不同:

const puppeteer = require('puppeteer');

async function solveAndInject() {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();

  // Capture GeeTest params
  let gt, challenge;
  page.on('response', async (response) => {
    if (response.url().includes('register') && response.status() === 200) {
      try {
        const data = await response.json();
        if (data.gt && data.challenge) {
          gt = data.gt;
          challenge = data.challenge;
        }
      } catch (e) {}
    }
  });

  await page.goto('https://staging.example.com/qa-login');
  await page.waitForSelector('.geetest_holder');

  // Solve with CaptchaAI (implementation from earlier)
  const solution = await solveCaptcha(gt, challenge, page.url());

  // Inject the three values
  await page.evaluate((sol) => {
    // Set hidden inputs
    const form = document.querySelector('form');
    ['geetest_challenge', 'geetest_validate', 'geetest_seccode'].forEach(name => {
      let input = document.querySelector(`input[name="${name}"]`);
      if (!input) {
        input = document.createElement('input');
        input.type = 'hidden';
        input.name = name;
        form.appendChild(input);
      }
      input.value = sol[name];
    });
  }, solution);

  await page.click('#submit-button');
}

第 4 步:在 Selenium 中提交结果(Python)

提示: Selenium 项目通常已经有 WebDriverWait 封装,直接复用即可,不用重新造轮子。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()
driver.get("https://staging.example.com/qa-login")

# Wait for GeeTest widget
WebDriverWait(driver, 15).until(
    EC.presence_of_element_located((By.CLASS_NAME, "geetest_holder"))
)

# Extract gt and challenge from the page
gt = driver.execute_script(
    "return document.querySelector('[data-gt]')?.dataset.gt"
)
challenge = driver.execute_script(
    "return document.querySelector('[data-challenge]')?.dataset.challenge"
)

# Solve with CaptchaAI
solution = solve_geetest(gt, challenge, driver.current_url)

# Inject via JavaScript
driver.execute_script(f"""
    var fields = {{
        'geetest_challenge': '{solution["geetest_challenge"]}',
        'geetest_validate': '{solution["geetest_validate"]}',
        'geetest_seccode': '{solution["geetest_seccode"]}'
    }};
    var form = document.querySelector('form');
    for (var name in fields) {{
        var input = document.querySelector('input[name="' + name + '"]');
        if (!input) {{
            input = document.createElement('input');
            input.type = 'hidden';
            input.name = name;
            form.appendChild(input);
        }}
        input.value = fields[name];
    }}
""")

driver.find_element(By.ID, "submit-button").click()

处理基于 XHR 提交的站点

有些站点不走表单 POST,而是通过 XHR 提交 GeeTest 结果。常见判断信号:

  • 点击提交后没有整页刷新
  • Network 面板里是 XHR/fetch 请求,不是整页 POST

遇到这种情况,需要拦截请求并修改请求体:

# Playwright: Intercept the XHR and inject values
def handle_route(route):
    if "login" in route.request.url and route.request.method == "POST":
        # Modify the POST data to include our solution
        post_data = route.request.post_data
        # Add GeeTest values to the request
        route.continue_(post_data=modified_data)
    else:
        route.continue_()

page.route("**/api/login**", handle_route)

常见故障排查

问题 原因 处理方式
GeeTest 验证失败 缺少三个值之一 检查三个值是否都已写入
提交前 challenge 已过期 提取和识别间隔太久 提取后立即调用 CaptchaAI
表单提交但值未生效 字段名写错 核对实际字段名
回调未触发 用了自定义回调名 initGeetest 配置
小部件仍显示未通过 状态未更新 手动触发成功回调

常见问题

提交后要不要把 GeeTest 小部件隐藏掉?

不需要。小部件显示状态不影响服务器校验,三个值写入表单后即可正常验证。

challenge 大概多久过期?

拿到 gtchallenge 后应尽快求解并写入,等待越久过期风险越高。

表单里已经有旧的 GeeTest 字段,会不会冲突?

不会。代码会先查找同名字段,存在则更新,不存在才新建。

相关文章

下一步

无论用哪种框架,思路都一样:先识别,再写入三个值。获取 CaptchaAI API Key,开始测试。

该文章已禁用评论。