Hướng Dẫn API

Hướng dẫn API và thông số CAPTCHA của GeeTest Slide

GeeTest v3 slide chỉ nhận task khi bạn gửi đủ và đúng bốn giá trị: gt, challenge, pageurl và (khi cần) api_server. Thiếu một trường, hoặc gửi challenge đã hết hạn, CaptchaAI trả về ERROR_CAPTCHA_UNSOLVABLE dù code không hề có lỗi logic nào. Bài này đi thẳng vào từng tham số — lấy ở đâu, định dạng ra sao — và cách gửi chính xác tới CaptchaAI bằng Python.

Một tình huống quen thuộc với các đội QA và automation ở Việt Nam: kiểm thử luồng đăng nhập của một hệ thống thương mại điện tử nội bộ (kiểu sàn bán lẻ như Shopee/Tiki) trước khi lên production. Trang login dùng GeeTest v3 slide, và script kiểm thử trong pipeline CI phải tự trích xuất gt/challenge mỗi lần chạy — vì challenge không tái sử dụng được giữa các lần chạy, và nếu script cache lại giá trị cũ, kịch bản kiểm thử sẽ fail ngẫu nhiên không rõ nguyên nhân.


Bốn tham số bắt buộc khi gửi GeeTest v3

Tham số Bắt buộc Mô tả
gt ID tài khoản GeeTest của trang, dạng hex 32 ký tự — cố định, lấy được từ mã nguồn trang hoặc phản hồi API
challenge Chuỗi thử thách riêng cho từng phiên — phải lấy mới trước mỗi lần gửi
pageurl URL đầy đủ của trang đang hiển thị CAPTCHA
api_server Không Tên miền phụ máy chủ GeeTest tùy chỉnh — chỉ cần khi trang không dùng endpoint mặc định

gt gần như không đổi theo thời gian nên có thể cache lại sau lần lấy đầu. challenge thì ngược lại: nó gắn với phiên hiện tại và hết hạn rất nhanh, nên script nào cache challenge là script đó sẽ dính lỗi khó debug nhất trong phần này.


Lấy gtchallenge từ trang đích

Đoạn code dưới thử hai cách: đọc trực tiếp từ HTML, rồi nếu không thấy thì gọi endpoint register-slide mà trang dùng để nạp CAPTCHA.

# extract_geetest_params.py
import requests
import re
import json

def extract_geetest_v3(page_url, session=None):
    """Extract GeeTest v3 gt and challenge from a page."""
    if session is None:
        session = requests.Session()
        session.headers["User-Agent"] = (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
        )

    resp = session.get(page_url, timeout=15)
    html = resp.text

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

    # Method 2: Find API endpoint that returns challenge
    api_match = re.search(r'(https?://[^"\']+register-slide[^"\']*)', html)

    challenge = None
    if api_match:
        api_url = api_match.group(1)
        api_resp = session.get(api_url, timeout=10)
        try:
            data = api_resp.json()
            challenge = data.get("challenge")
            gt = gt or data.get("gt")
        except json.JSONDecodeError:
            pass

    if not challenge:
        # Try embedded challenge
        ch_match = re.search(r'challenge["\']?\s*[:=]\s*["\']([a-f0-9]+)', html)
        challenge = ch_match.group(1) if ch_match else None

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

# Usage
params = extract_geetest_v3("https://staging.example.com/qa-login")
print(f"gt: {params['gt']}")
print(f"challenge: {params['challenge']}")

Nếu gt trả về None, gần như chắc chắn trang đang nạp widget qua JavaScript sau khi DOM đã render — xem phần khắc phục sự cố bên dưới để biết cách xử lý.


Gửi task GeeTest sang CaptchaAI

Có đủ ba giá trị rồi thì gửi task tới in.php với method=geetest, rồi polling res.php cho tới khi có kết quả. GeeTest thường giải trong 10-20 giây.

# solve_geetest.py
import requests
import time
import os

def solve_geetest(gt, challenge, pageurl, api_server=None):
    """Solve GeeTest v3 slide CAPTCHA via CaptchaAI."""
    api_key = os.environ["CAPTCHAAI_API_KEY"]

    payload = {
        "key": api_key,
        "method": "geetest",
        "gt": gt,
        "challenge": challenge,
        "pageurl": pageurl,
        "json": 1,
    }

    if api_server:
        payload["api_server"] = api_server

    # Submit
    resp = requests.post(
        "https://ocr.captchaai.com/in.php",
        data=payload,
        timeout=30,
    )
    result = resp.json()

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

    task_id = result["request"]

    # Poll — GeeTest typically solves in 10-20 seconds
    time.sleep(10)
    for _ in range(30):
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=15)
        data = resp.json()

        if data.get("status") == 1:
            return data["request"]  # Returns challenge, validate, seccode
        if data["request"] != "CAPCHA_NOT_READY":
            raise RuntimeError(data["request"])
        time.sleep(5)

    raise TimeoutError("GeeTest solve timeout")

Vòng lặp polling ở trên chờ tối đa 30 × 5 giây = 150 giây trước khi báo timeout — đủ dư so với thời gian giải thực tế, nhưng bạn nên log lại task_id để tra cứu nếu cần đối chiếu với dashboard CaptchaAI.


Dùng kết quả để vượt qua CAPTCHA của trang đích

Kết quả trả về gồm ba trường phải gửi lại cho endpoint xác thực của trang đích: geetest_challenge, geetest_validate, geetest_seccode. Thiếu một trường là trang sẽ từ chối.

# submit_solution.py
import json

def submit_geetest_solution(session, validation_url, solution, original_challenge):
    """Submit GeeTest solution to the target site."""
    # Parse solution if string
    if isinstance(solution, str):
        solution = json.loads(solution)

    payload = {
        "geetest_challenge": solution.get("challenge", original_challenge),
        "geetest_validate": solution.get("validate", ""),
        "geetest_seccode": solution.get("seccode", ""),
    }

    resp = session.post(validation_url, data=payload, timeout=30)
    return resp

# Complete flow
def full_geetest_flow(page_url, validation_url):
    import requests
    from extract_geetest_params import extract_geetest_v3

    session = requests.Session()
    session.headers["User-Agent"] = (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
    )

    # Step 1: Extract parameters
    params = extract_geetest_v3(page_url, session)
    print(f"gt: {params['gt']}, challenge: {params['challenge'][:16]}...")

    # Step 2: Solve
    solution = solve_geetest(
        params["gt"], params["challenge"], params["pageurl"],
    )
    print("Solved!")

    # Step 3: Submit
    resp = submit_geetest_solution(
        session, validation_url, solution, params["challenge"],
    )
    print(f"Validation response: {resp.status_code}")
    return resp

Hàm full_geetest_flow gộp cả ba bước ở trên thành một lệnh gọi — hữu ích khi bạn muốn nhúng thẳng vào test case hoặc job scraping định kỳ.


Vì sao challenge phải luôn mới

challenge gắn với phiên hiện tại và hết hạn rất nhanh — thường trong 60-120 giây. Cache lại challenge từ lần chạy trước, dù chỉ vài chục giây, là nguyên nhân phổ biến nhất khiến CaptchaAI trả ERROR_CAPTCHA_UNSOLVABLE.

# fresh_challenge.py
import time

def get_fresh_challenge(session, register_url):
    """Always fetch a fresh challenge before solving."""
    resp = session.get(register_url, timeout=10)
    data = resp.json()

    challenge = data.get("challenge")
    if not challenge:
        raise ValueError("No challenge returned")

    return challenge

def solve_with_fresh_challenge(session, gt, register_url, pageurl):
    """Ensure challenge is fresh before submitting to CaptchaAI."""
    challenge = get_fresh_challenge(session, register_url)

    # Submit immediately — don't let it expire
    solution = solve_geetest(gt, challenge, pageurl)
    return solution

Quy tắc chính: trích xuất challenge và gửi tới CaptchaAI cách nhau vài giây, không hơn. Một challenge cũ luôn thất bại — không có ngoại lệ.


Khi nào cần khai báo api_server

Phần lớn trang dùng endpoint mặc định api.geetest.com nên bạn có thể bỏ qua api_server. Chỉ khi trang route CAPTCHA qua một subdomain riêng — thường thấy ở các trang có hạ tầng theo khu vực — bạn mới cần truyền thêm giá trị này.

# The api_server parameter specifies a custom GeeTest backend
# Default: api.geetest.com
# Custom examples: api-na.geetest.com, api.geetest.com/ajax-custom

solution = solve_geetest(
    gt="abc123...",
    challenge="def456...",
    pageurl="https://staging.example.com/qa-login",
    api_server="api-na.geetest.com",  # North America endpoint
)

Để biết trang có dùng endpoint tùy chỉnh hay không, mở tab Network của DevTools, lọc theo geetest.com và xem domain thực tế của request đăng ký challenge.


Các lỗi thường gặp và cách xử lý

Vấn đề Nguyên nhân Cách xử lý
ERROR_CAPTCHA_UNSOLVABLE challenge Lấy challenge mới ngay trước khi gửi, không cache
validate trống Sai phiên bản API GeeTest v4 dùng version=4 — nhưng CaptchaAI hiện chưa hỗ trợ giải v4, chỉ v3
Trang từ chối kết quả Thiếu seccode Đảm bảo cả ba trường challenge/validate/seccode đều được gửi lại
Không tìm thấy gt trong HTML Widget được nạp qua JavaScript Dùng Selenium/Playwright render trang, hoặc bắt request XHR tới endpoint đăng ký

Chi phí gửi GeeTest slide qua CaptchaAI

CaptchaAI tính phí theo thread đang chạy song song, không tính theo từng lần giải — một thread giải xong task này là rảnh ngay để nhận task tiếp theo, không giới hạn số lượt giải trong tháng. Với khối lượng test/scraping vừa phải, gói BASIC ($15/tháng, 5 thread) đã đủ chạy song song 5 task GeeTest cùng lúc; đội cần thông lượng lớn hơn cho pipeline CI hoặc job thu thập dữ liệu định kỳ có thể lên STANDARD ($30/tháng, 15 thread) hoặc cao hơn tùy tải thực tế.


Câu hỏi thường gặp

GeeTest v3 khác GeeTest v4 ở điểm nào, CaptchaAI có giải được v4 không?

Khác cấu trúc tham số và cách nạp widget. CaptchaAI hiện chưa hỗ trợ giải GeeTest v4 — tính năng này đang trong lộ trình "sắp ra mắt". Bài này chỉ áp dụng cho GeeTest v3 slide.

gtchallenge khác nhau thế nào?

gt là ID tài khoản GeeTest của trang — gần như cố định, cache được. challenge sinh mới theo từng phiên và phải trích xuất lại ngay trước mỗi lần gửi.

Gửi GeeTest slide qua CaptchaAI tốn bao nhiêu?

Không tính theo lượt giải. Bạn trả theo số thread chạy song song — gói BASIC $15/tháng đã có 5 thread, đủ cho phần lớn khối lượng test hoặc scraping quy mô nhỏ.

Không tìm thấy tham số gt trong mã nguồn HTML thì làm sao?

Nhiều khả năng trang nạp widget GeeTest bằng JavaScript sau khi tải trang. Render trang bằng Selenium/Playwright rồi đọc lại DOM, hoặc theo dõi tab Network để bắt request XHR trả về gt/challenge.

challenge còn hiệu lực trong bao lâu?

Thường 60-120 giây. Trích xuất xong nên gửi tới CaptchaAI ngay — càng để lâu, xác suất bị từ chối vì challenge hết hạn càng cao.


Hướng dẫn liên quan


Nắm chắc bộ tham số GeeTest v3 rồi thì phần còn lại chỉ là lặp lại quy trình — bắt đầu với CaptchaAI.

Os comentários estão desativados para este artigo.