Tutorials

Solving GeeTest v3 with Python and CaptchaAI API

GeeTest v3 is a slider-based CAPTCHA commonly found on Chinese and international platforms. Unlike reCAPTCHA, GeeTest requires extracting specific challenge parameters (gt, challenge) before solving. This guide shows the complete flow using Python and CaptchaAI's GeeTest solver — which achieves 100% success rate.


Prerequisites

pip install requests

You need:

  • A CaptchaAI API key from captchaai.com
  • The target page URL
  • GeeTest gt (account ID) and challenge (per-session token)

GeeTest v3 parameters

GeeTest v3 requires two parameters for solving:

Parameter Description Where to find
gt GeeTest account ID (32-char hex) Static in page source or API response
challenge Per-session challenge token (32-char hex) Dynamic — must be fetched fresh per solve

How parameters flow


1. Page loads → JavaScript requests GeeTest API
2. API returns: { gt: "abc...", challenge: "def...", success: 1 }
3. User completes slider
4. GeeTest returns: { geetest_challenge, geetest_validate, geetest_seccode }
5. Server verifies with GeeTest backend

Step 1: Extract GeeTest parameters

Method A: From page HTML

import re
import requests

def extract_geetest_from_html(url):
    """Extract GeeTest gt and challenge from page HTML."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
    }
    response = requests.get(url, headers=headers, timeout=15)
    html = response.text

    # Extract gt (account ID)
    gt_match = re.search(r'gt\s*[:=]\s*["\']([a-f0-9]{32})["\']', html)
    gt = gt_match.group(1) if gt_match else None

    # Extract challenge
    challenge_match = re.search(
        r'challenge\s*[:=]\s*["\']([a-f0-9]{32})["\']', html
    )
    challenge = challenge_match.group(1) if challenge_match else None

    return {"gt": gt, "challenge": challenge}

Method B: From GeeTest register API (more common)

def extract_geetest_from_api(register_url):
    """Fetch fresh GeeTest parameters from the site's register endpoint."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
        "Referer": "https://example.com/login",
    }

    response = requests.get(register_url, headers=headers, timeout=15)
    data = response.json()

    # Common response format:
    # { "gt": "abc123...", "challenge": "def456...", "success": 1 }
    return {
        "gt": data.get("gt"),
        "challenge": data.get("challenge"),
        "success": data.get("success"),
    }


# Example: Many sites have a /geetest/register endpoint
params = extract_geetest_from_api("https://example.com/api/geetest/register")
print(f"GT: {params['gt']}")
print(f"Challenge: {params['challenge']}")

Method C: Intercept from network requests

from selenium import webdriver
from selenium.webdriver.common.by import By
import json

def extract_geetest_from_network(url):
    """Use Selenium to intercept GeeTest register call."""
    options = webdriver.ChromeOptions()
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.set_capability("goog:loggingPrefs", {"performance": "ALL"})

    driver = webdriver.Chrome(options=options)
    driver.get(url)

    import time
    time.sleep(5)  # Wait for GeeTest to initialize

    # Check performance logs for GeeTest API calls
    logs = driver.get_log("performance")
    for entry in logs:
        message = json.loads(entry["message"])["message"]
        if message["method"] == "Network.responseReceived":
            resp_url = message["params"]["response"]["url"]
            if "geetest" in resp_url or "gt=" in resp_url:
                # Found the GeeTest register call
                request_id = message["params"]["requestId"]
                body = driver.execute_cdp_cmd(
                    "Network.getResponseBody", {"requestId": request_id}
                )
                data = json.loads(body["body"])
                driver.quit()
                return {"gt": data.get("gt"), "challenge": data.get("challenge")}

    driver.quit()
    return None

Step 2: Submit to CaptchaAI

import requests

API_KEY = "YOUR_API_KEY"

def submit_geetest(gt, challenge, page_url):
    """Submit GeeTest v3 solving task to CaptchaAI."""
    response = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "geetest",
        "gt": gt,
        "challenge": challenge,
        "pageurl": page_url,
        "json": 1,
    })

    data = response.json()

    if data.get("status") != 1:
        raise Exception(f"Submit failed: {data.get('request')}")

    return data["request"]

Step 3: Poll for the result

GeeTest returns three values instead of a single token:

import time

def poll_geetest_result(task_id, timeout=120):
    """Poll CaptchaAI for GeeTest v3 solution."""
    start = time.time()

    while time.time() - start < timeout:
        time.sleep(5)

        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        }).json()

        if result.get("status") == 1:
            # GeeTest returns three values
            answer = result["request"]
            # Format: "geetest_challenge:xxx|geetest_validate:xxx|geetest_seccode:xxx"
            return parse_geetest_response(answer)

        if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
            raise Exception("GeeTest could not be solved")

    raise TimeoutError("Solve timed out")


def parse_geetest_response(response_str):
    """Parse GeeTest response into individual components."""
    parts = {}
    for pair in response_str.split("|"):
        if ":" in pair:
            key, value = pair.split(":", 1)
            parts[key] = value
    return parts

Complete working example

import re
import time
import requests

API_KEY = "YOUR_API_KEY"
TARGET_URL = "https://example.com/login"
GEETEST_REGISTER_URL = "https://example.com/api/geetest/register"


def get_geetest_params(session, register_url):
    """Fetch fresh GeeTest parameters."""
    response = session.get(register_url, timeout=15)
    data = response.json()
    return data["gt"], data["challenge"]


def solve_geetest(gt, challenge, page_url):
    """Solve GeeTest v3 via CaptchaAI."""
    # Submit
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "geetest",
        "gt": gt,
        "challenge": challenge,
        "pageurl": page_url,
        "json": 1,
    })

    data = submit.json()
    if data.get("status") != 1:
        raise Exception(f"Submit error: {data.get('request')}")

    task_id = data["request"]
    print(f"Task submitted: {task_id}")

    # Poll
    for _ in range(30):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        }).json()

        if result.get("status") == 1:
            return result["request"]

    raise TimeoutError("Solve timed out")


# --- Main flow ---
session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 Chrome/120.0.0.0",
    "Accept": "application/json, text/html, */*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
})

# 1. Get fresh challenge parameters
gt, challenge = get_geetest_params(session, GEETEST_REGISTER_URL)
print(f"GT: {gt}")
print(f"Challenge: {challenge}")

# 2. Solve GeeTest
solution = solve_geetest(gt, challenge, TARGET_URL)
print(f"Solution received")

# 3. Submit to target site
# The exact submission format varies by site
form_response = session.post(TARGET_URL, data={
    "geetest_challenge": challenge,
    "geetest_validate": solution.split("|")[1].split(":")[1] if "|" in solution else solution,
    "geetest_seccode": solution.split("|")[2].split(":")[1] if "|" in solution else f"{solution}|jordan",
    "username": "user@example.com",
    "password": "password123",
})
print(f"Login status: {form_response.status_code}")

GeeTest response values

The CaptchaAI response contains three components that the target site validates:

Value Description Format
geetest_challenge The original challenge token 32-char hex string
geetest_validate Validation hash 32-char hex string
geetest_seccode Security code {validate}\|jordan

Constructing the seccode

The geetest_seccode is typically the geetest_validate value followed by |jordan:

validate = "abc123..."  # From CaptchaAI response
seccode = f"{validate}|jordan"

Common GeeTest v3 patterns

Pattern 1: JSON API submission

response = session.post("https://example.com/api/verify", json={
    "geetest_challenge": challenge,
    "geetest_validate": validate,
    "geetest_seccode": seccode,
    "action": "login",
})

Pattern 2: Form POST

response = session.post("https://example.com/login", data={
    "geetest_challenge": challenge,
    "geetest_validate": validate,
    "geetest_seccode": seccode,
    "email": "user@example.com",
    "password": "pass123",
})

Pattern 3: Combined with CSRF token

# Some sites require CSRF token alongside GeeTest
csrf_match = re.search(r'name="csrf_token" value="([^"]+)"', page_html)
csrf_token = csrf_match.group(1) if csrf_match else ""

response = session.post("https://example.com/login", data={
    "csrf_token": csrf_token,
    "geetest_challenge": challenge,
    "geetest_validate": validate,
    "geetest_seccode": seccode,
    "username": "user",
})

Error handling

class GeeTestSolveError(Exception):
    pass

def solve_geetest_robust(gt, challenge, page_url, max_retries=3):
    """Solve GeeTest with robust error handling."""
    for attempt in range(1, max_retries + 1):
        try:
            submit = requests.post(
                "https://ocr.captchaai.com/in.php",
                data={
                    "key": API_KEY,
                    "method": "geetest",
                    "gt": gt,
                    "challenge": challenge,
                    "pageurl": page_url,
                    "json": 1,
                },
                timeout=30,
            )
            submit.raise_for_status()
            data = submit.json()

            if data.get("status") != 1:
                error = data.get("request", "Unknown")
                if error in ("ERROR_WRONG_USER_KEY", "ERROR_ZERO_BALANCE"):
                    raise GeeTestSolveError(f"Fatal: {error}")
                raise GeeTestSolveError(f"Submit: {error}")

            task_id = data["request"]

            for _ in range(30):
                time.sleep(5)
                result = requests.get("https://ocr.captchaai.com/res.php", params={
                    "key": API_KEY,
                    "action": "get",
                    "id": task_id,
                    "json": 1,
                }, timeout=30).json()

                if result.get("status") == 1:
                    return result["request"]
                if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
                    break

            print(f"Attempt {attempt} failed")

        except requests.RequestException as e:
            print(f"Network error attempt {attempt}: {e}")
            if attempt == max_retries:
                raise GeeTestSolveError(f"Network failure: {e}")
            time.sleep(10)

    raise GeeTestSolveError(f"Failed after {max_retries} attempts")

Troubleshooting

Symptom Cause Fix
"Invalid gt" error GT parameter wrong Verify 32-char hex format
Challenge expired Challenge token is per-session, expires fast Fetch fresh challenge immediately before solving
Solve succeeds but site rejects Challenge reused or expired Use fresh challenge per attempt
"geetest_validate" empty Response parsing error Check CaptchaAI response format
GeeTest API returns success=0 GeeTest in fallback mode Check if site uses offline challenge

Frequently asked questions

How fresh does the challenge need to be?

The challenge token expires within 60-120 seconds. Always fetch a fresh challenge immediately before submitting to CaptchaAI. Never cache or reuse challenges.

What's the difference between GeeTest v3 and v4?

GeeTest v3 uses gt + challenge parameters. GeeTest v4 uses captcha_id + lot_number. This guide covers v3 only.

What's the solve time?

GeeTest v3 typically solves in 5-15 seconds. CaptchaAI achieves a 100% success rate on GeeTest v3.

Can I solve GeeTest offline (fallback) mode?

GeeTest fallback mode is used when GeeTest's servers are unreachable. The challenge type changes. Contact CaptchaAI support for fallback mode handling.


Summary

Solving GeeTest v3 with Python requires: fetch fresh gt and challenge parameters from the site's register API, submit to CaptchaAI using method=geetest, then submit the three result values (geetest_challenge, geetest_validate, geetest_seccode) to the target site. Always use fresh challenge tokens — they expire in under 2 minutes.

Full Working Code

Complete runnable examples for this article in Python, Node.js, PHP, Go, Java, C#, Ruby, Rust, Kotlin & Bash.

View on GitHub →

Discussions (0)

No comments yet.