Khắc Phục Sự Cố

CAPTCHA tự động hóa trình duyệt không thành công nhưng API vẫn hoạt động: Hướng dẫn gỡ lỗi

Test bằng requests hoặc cURL thì token CaptchaAI hợp lệ, nhưng cắm vào Selenium hay Playwright thì form vẫn báo lỗi — gần như luôn là một trong sáu nguyên nhân dưới đây, không phải lỗi CaptchaAI. API chỉ trả token; trình duyệt có chấp nhận hay không lại phụ thuộc JavaScript của trang.

Ví dụ thường gặp: team QA outsource ở TP.HCM test checkout trên staging bằng Playwright, nhận token hợp lệ từ CaptchaAI, nhưng nút "Đặt hàng" vẫn xám ngắt vì callback JavaScript chưa được gọi.

Kiểm tra nhanh:

  • Token có set đúng phần tử site đang đọc, hay set nhầm phần tử ẩn?
  • Callback của widget đã gọi thủ công chưa?
  • pageurl gửi cho CaptchaAI có đúng URL hiện tại, kể cả khi CAPTCHA nằm trong iframe?

Token kẹt ở đâu giữa API và trình duyệt

Vấn đề Triệu chứng Nguyên nhân
Token sai phần tử Form thiếu token Sai bộ chọn textarea
Token hết hạn CAPTCHA hiện lại Giải và gửi cách nhau quá lâu
Callback chưa kích hoạt Nút submit vẫn disable Thiếu lệnh gọi grecaptcha
pageurl sai Token không hợp lệ cho domain URL không khớp thực tế
Lớp chống bot khác Bị chặn dù token đã nhận Fingerprint trình duyệt tự động hóa

Cách 1: Set đúng token cho reCAPTCHA v2

Token phải nằm trong textarea g-recaptcha-response VÀ callback phải được gọi — thiếu một trong hai, form vẫn coi như chưa giải xong:

Nếu trang có nhiều widget reCAPTCHA, querySelectorAll bên dưới set token cho tất cả — kiểm tra lại có đúng ý bạn không.

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

def inject_recaptcha_token(driver, token):
    """Properly inject reCAPTCHA v2 token in browser."""

    # Step 1: Make the response textarea visible and set the value
    driver.execute_script("""
        // Find all response textareas (may be multiple on page)
        var textareas = document.querySelectorAll('[name="g-recaptcha-response"]');
        textareas.forEach(function(ta) {
            ta.style.display = 'block';
            ta.value = arguments[0];
        });

        // Also set via ID if present
        var byId = document.getElementById('g-recaptcha-response');
        if (byId) {
            byId.style.display = 'block';
            byId.value = arguments[0];
        }
    """, token)

    # Step 2: Trigger the callback
    driver.execute_script("""
        // Try standard callback
        if (typeof ___grecaptcha_cfg !== 'undefined') {
            var clients = ___grecaptcha_cfg.clients;
            for (var key in clients) {
                var client = clients[key];
                // Navigate nested structure to find callback
                for (var prop in client) {
                    var val = client[prop];
                    if (val && typeof val === 'object') {
                        for (var subprop in val) {
                            var subval = val[subprop];
                            if (subval && typeof subval === 'object' && subval.callback) {
                                subval.callback(arguments[0]);
                                return;
                            }
                        }
                    }
                }
            }
        }
        // Fallback: try common callback names
        if (typeof onCaptchaSuccess === 'function') onCaptchaSuccess(arguments[0]);
        else if (typeof captchaCallback === 'function') captchaCallback(arguments[0]);
    """, token)

# Usage
driver = webdriver.Chrome()
driver.get("https://example.com/form")
# ... solve CAPTCHA via CaptchaAI ...
inject_recaptcha_token(driver, token)
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()

Cách 2: Xử lý form chỉ mở khóa sau callback

Nhiều form giữ disabled đến khi callback chạy xong — set value textarea thôi chưa đủ:

def find_and_trigger_callback(driver, token):
    """Find the reCAPTCHA callback and trigger it."""

    # Method 1: Extract callback from data attribute
    callback_name = driver.execute_script("""
        var widget = document.querySelector('.g-recaptcha');
        if (widget) {
            return widget.getAttribute('data-callback');
        }
        return null;
    """)

    if callback_name:
        driver.execute_script(f"window['{callback_name}'](arguments[0]);", token)
        return True

    # Method 2: Extract from grecaptcha config
    triggered = driver.execute_script("""
        try {
            var widgetId = 0;
            var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
            if (typeof callback === 'function') {
                callback(arguments[0]);
                return true;
            }
        } catch(e) {}
        return false;
    """, token)

    return triggered

Cách 3: Canh thời gian — giải xong dùng ngay

Token reCAPTCHA v2 có TTL ngắn. Giải xong mới đi log hay chuyển tab trước khi submit — token gần như chắc chắn hết hạn:

import requests
import time

def solve_and_inject_fast(driver, api_key, sitekey, page_url):
    """Solve CAPTCHA and inject token with minimal delay."""
    # Submit solve request
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    }, timeout=30)
    task_id = resp.json()["request"]

    # Poll for result
    time.sleep(15)
    for _ in range(24):
        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:
            token = data["request"]

            # IMMEDIATELY inject — don't wait
            inject_recaptcha_token(driver, token)

            # Submit form within 5 seconds
            time.sleep(0.5)
            return True

        if data["request"] != "CAPCHA_NOT_READY":
            raise RuntimeError(data["request"])
        time.sleep(5)

    raise TimeoutError("Solve timeout")

Cách 4: Lấy đúng pageurl cho SPA và iframe

CAPTCHA trong iframe không đổi URL tab. Lấy pageurl từ biến lưu sẵn thay vì URL hiện tại thường khiến token bị từ chối vì domain không khớp:

driver.current_url có thể đổi ngay sau khi router chuyển trang — lấy pageurl càng sát thời điểm gọi API càng an toàn.

def get_correct_pageurl(driver):
    """Get the URL that reCAPTCHA actually sees."""

    # Check if CAPTCHA is in an iframe
    iframes = driver.find_elements(By.TAG_NAME, "iframe")

    for iframe in iframes:
        src = iframe.get_attribute("src") or ""
        if "recaptcha" in src or "anchor" in src:
            # CAPTCHA is in iframe — use parent page URL
            return driver.current_url

    # For SPAs, use current URL (may differ from initial load)
    return driver.current_url

# WRONG — using the URL you navigated to
pageurl = "https://example.com/form"  # May have redirected

# CORRECT — using the URL the browser is actually on
pageurl = get_correct_pageurl(driver)

Cách 5: Trang có lớp chống bot riêng, tách khỏi CAPTCHA

Token hợp lệ không đồng nghĩa trình duyệt được chấp nhận. Site có thể chạy thêm lớp kiểm tra hành vi song song — token qua được nhưng request sau vẫn nhận 403:

Cấu hình trình duyệt kiểm thử chuẩn để giảm tín hiệu tự động hóa trong QA nội bộ.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

def create_stealth_driver():
    """Create a browser that avoids basic bot detection."""
    options = Options()

    # Remove automation indicators
    options.add_argument("")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option("useAutomationExtension", False)

    # Set realistic window size
    options.add_argument("--window-size=1920,1080")

    driver = webdriver.Chrome(options=options)

    # Override navigator.webdriver
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
    })

    return driver

driver = create_stealth_driver()

Cách 6: Set token cho Cloudflare Turnstile

Turnstile không dùng chung selector với reCAPTCHA v2 — copy nguyên code Cách 1 là lỗi hay gặp nhất:

def inject_turnstile_token(driver, token):
    """Inject Turnstile token into the page."""
    driver.execute_script("""
        // Find Turnstile response input
        var inputs = document.querySelectorAll(
            'input[name="cf-turnstile-response"], ' +
            'input[name="g-recaptcha-response"]'
        );

        inputs.forEach(function(input) {
            input.value = arguments[0];
        });

        // Trigger change event
        inputs.forEach(function(input) {
            input.dispatchEvent(new Event('change', { bubbles: true }));
            input.dispatchEvent(new Event('input', { bubbles: true }));
        });

        // Try Turnstile callback
        if (window.turnstile) {
            var widgets = document.querySelectorAll('[data-callback]');
            widgets.forEach(function(w) {
                var cb = w.getAttribute('data-callback');
                if (typeof window[cb] === 'function') {
                    window[cb](arguments[0]);
                }
            });
        }
    """, token)

Script kiểm tra nhanh khi vẫn bí

Chạy ngay sau khi set token để biết chính xác bước nào thiếu:

def debug_captcha_injection(driver, token):
    """Print debug info to diagnose injection failures."""
    info = driver.execute_script("""
        var result = {};

        // Check textarea exists
        var ta = document.querySelector('[name="g-recaptcha-response"]');
        result.textarea_found = !!ta;
        result.textarea_value_set = ta ? ta.value.length > 0 : false;

        // Check reCAPTCHA loaded
        result.grecaptcha_loaded = typeof grecaptcha !== 'undefined';

        // Check for callback
        var widget = document.querySelector('.g-recaptcha');
        result.has_data_callback = widget ? !!widget.getAttribute('data-callback') : false;
        result.callback_name = widget ? widget.getAttribute('data-callback') : null;

        // Current URL
        result.current_url = window.location.href;

        // Check for errors in console (basic)
        result.has_submit_button = !!document.querySelector('button[type=submit], input[type=submit]');

        return result;
    """)

    for key, value in info.items():
        print(f"  {key}: {value}")

    return info

Tra nhanh theo triệu chứng

Triệu chứng Nguyên nhân Cách sửa
Set token nhưng form từ chối Callback chưa kích hoạt Tìm và gọi đúng hàm callback
Nút submit vẫn disable Callback mở khóa nút Kích hoạt callback sau khi set token
"CAPTCHA không hợp lệ" sau submit Token hết hạn Rút ngắn thời gian giải và gửi
403 sau submit Lớp chống bot khác, không phải CAPTCHA Kiểm tra cấu hình trình duyệt kiểm thử
Ổn với requests, lỗi trong trình duyệt pageurl khác nhau Dùng driver.current_url làm pageurl

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

Vì sao token qua API thành công nhưng trình duyệt vẫn từ chối?

Thường do callback của widget chưa kích hoạt — set textarea thôi chưa đủ để bật nút submit.

Playwright và Puppeteer có lỗi set token giống Selenium không?

Có, vì nguyên nhân nằm ở JavaScript của widget — đổi execute_script thành page.evaluate(), logic callback giữ nguyên.

Làm sao biết token đã hết hạn trước khi submit?

Đo thời gian từ lúc res.php trả status: 1 đến lúc submit; quá vài chục giây thì giải lại token.

Headless có làm tăng khả năng bị từ chối token không?

Có, với site kiểm tra hành vi mạnh. Thử headful trước khi chuyển sang headless.

Retry nhiều lần khi debug có tốn thêm chi phí không?

Không tính thêm theo lần — CaptchaAI tính theo thread, giải không giới hạn trong tháng. Nhưng mỗi retry vẫn chiếm một thread, gói nhỏ như BASIC ($15/tháng, 5 thread) debug dồn dập dễ nghẽn job khác.


Hướng dẫn liên quan


Bước tiếp theo

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