Khắc Phục Sự Cố

Xử lý reCAPTCHA v2 và v3 trên cùng một trang

Script của bạn gửi đúng token reCAPTCHA v3, nhưng form vẫn không submit được — kiểm tra HTML thì thấy một ô checkbox reCAPTCHA v2 vừa xuất hiện mà code chưa xử lý. Đây là dấu hiệu kinh điển của triển khai kép: v3 chấm điểm ẩn trước, chỉ khi điểm quá thấp trang mới bật v2 xác minh thêm. Không nhận diện đúng hai lớp này là lý do phổ biến nhất khiến automation "đứng hình" ở bước cuối. Bài viết chỉ ra cách phát hiện triển khai kép, chiến lược giải từng phiên bản, và các cạm bẫy khi hai sitekey xuất hiện cùng lúc trên một trang.


Vì sao trang web chạy song song v2 và v3

Cơ chế hoạt động khá đơn giản, thể hiện qua luồng dưới đây:

User visits page
    ↓
reCAPTCHA v3 runs invisibly in background
    ↓
Score returned to server (e.g., 0.4)
    ↓
Score below threshold (e.g., < 0.7)?
    ├─ YES → Show reCAPTCHA v2 checkbox/image challenge
    └─ NO  → Allow action without visible CAPTCHA

Cách phối hợp này mang lại lợi ích cho cả hai phía:

  • Phần lớn người dùng thật (điểm v3 cao) không thấy CAPTCHA nào
  • Traffic đáng ngờ (điểm v3 thấp) mới bị chặn lại bằng ô v2
  • Đội vận hành trang web tự chỉnh ngưỡng điểm để cân bằng ma sát và bảo mật

Trường hợp thực tế: một đội QA outsourcing ở TP.HCM test luồng đăng nhập staging cho khách hàng thương mại điện tử (kiểu Shopee, Lazada) từng mất nửa ngày debug vì script chỉ gửi token v3 — điểm v3 luôn thấp trên môi trường headless nên v2 bật liên tục mà code không xử lý. Vấn đề hết sau khi thêm bước kiểm tra phản hồi server và gọi CaptchaAI lần hai cho v2.


Các mẫu triển khai reCAPTCHA kép

Không phải trang nào cũng ghép v2 và v3 giống nhau. Ba mẫu thường gặp:

Mẫu Tín hiệu nhận biết
v3 trước, fallback v2 Container v2 ẩn (display:none), hiện khi điểm thấp
Sitekey riêng theo hành động Mỗi action gọi execute() với sitekey riêng
Một script xử lý cả hai Cùng một <script> gọi cả execute lẫn render

v3 đánh giá trước, v2 xuất hiện khi điểm thấp — mẫu phổ biến nhất:

<!-- Both scripts loaded -->
<script src="https://www.google.com/recaptcha/api.js?render=V3_SITE_KEY"></script>
<script src="https://www.google.com/recaptcha/api.js?render=explicit" async defer></script>

<form id="loginForm">
    <!-- v2 widget (hidden initially) -->
    <div id="recaptcha-v2-container" style="display:none;">
        <div class="g-recaptcha" data-sitekey="V2_SITE_KEY"></div>
    </div>
    <button type="submit">Login</button>
</form>

<script>
// First attempt: v3 invisible
grecaptcha.ready(function() {
    grecaptcha.execute('V3_SITE_KEY', {action: 'login'}).then(function(v3Token) {
        fetch('/api/verify-v3', {
            method: 'POST',
            body: JSON.stringify({token: v3Token})
        })
        .then(r => r.json())
        .then(data => {
            if (data.score < 0.7) {
                // Score too low → show v2 fallback
                document.getElementById('recaptcha-v2-container').style.display = 'block';
                grecaptcha.render('recaptcha-v2-container', {sitekey: 'V2_SITE_KEY'});
            } else {
                // Score OK → submit form directly
                document.getElementById('loginForm').submit();
            }
        });
    });
});
</script>

Sitekey riêng theo từng hành động — v3 theo dõi thụ động, v2 dành cho hành động nhạy cảm:

Homepage → v3 only (passive score)
Login page → v3 assessment, v2 fallback
Checkout → v2 always (high security)
Contact form → v3 only

Một script xử lý cả hai phiên bản — Google cho tải một tập lệnh duy nhất thay vì hai script riêng:

<script src="https://www.google.com/recaptcha/api.js?render=V3_SITE_KEY"></script>
<script>
    // v3 execute
    grecaptcha.execute('V3_SITE_KEY', {action: 'login'});

    // v2 render (uses a different site key)
    grecaptcha.render('v2-container', {sitekey: 'V2_SITE_KEY'});
</script>

Phát hiện triển khai kép bằng code

Trước khi viết logic giải, bạn cần xác định chính xác trang đang dùng phiên bản nào — quét HTML là cách nhanh nhất, không cần mở trình duyệt.

Script Python — regex bên dưới bắt cả tham số render (v3) lẫn thuộc tính data-sitekey (v2), rồi so sánh hai key để tránh nhầm lẫn khi trang chỉ dùng một phiên bản:

import requests
import re

def detect_dual_recaptcha(url):
    """Detect if a page uses both reCAPTCHA v2 and v3."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/120.0.0.0 Safari/537.36",
    }
    html = requests.get(url, headers=headers, timeout=15).text

    result = {
        "has_v3": False,
        "has_v2": False,
        "v3_site_key": None,
        "v2_site_key": None,
        "dual": False,
        "pattern": None,
    }

    # Detect v3 (render parameter or enterprise.execute)
    v3_match = re.search(r"api\.js\?render=([A-Za-z0-9_-]+)", html)
    if v3_match and v3_match.group(1) != "explicit":
        result["has_v3"] = True
        result["v3_site_key"] = v3_match.group(1)

    # Detect v3 in execute calls
    v3_execute = re.search(
        r"grecaptcha\.(?:enterprise\.)?execute\s*\(\s*['\"]([^'\"]+)['\"]",
        html,
    )
    if v3_execute:
        result["has_v3"] = True
        if not result["v3_site_key"]:
            result["v3_site_key"] = v3_execute.group(1)

    # Detect v2 (g-recaptcha class or explicit render)
    v2_match = re.search(r'data-sitekey="([^"]+)"', html)
    if v2_match:
        key = v2_match.group(1)
        if key != result.get("v3_site_key"):
            result["has_v2"] = True
            result["v2_site_key"] = key

    # Check for explicit v2 render
    v2_render = re.search(
        r"grecaptcha\.render\s*\([^,]+,\s*\{[^}]*sitekey:\s*['\"]([^'\"]+)",
        html,
    )
    if v2_render:
        result["has_v2"] = True
        if not result["v2_site_key"]:
            result["v2_site_key"] = v2_render.group(1)

    result["dual"] = result["has_v3"] and result["has_v2"]

    if result["dual"]:
        # Determine pattern
        if "display:none" in html or "display: none" in html:
            result["pattern"] = "v3_pre_assessment_v2_fallback"
        else:
            result["pattern"] = "v2_v3_simultaneous"

    return result

detection = detect_dual_recaptcha("https://staging.example.com/qa-login")
print(detection)

Script Node.js — áp dụng cùng logic, phù hợp nếu pipeline automation chạy trên JavaScript:

const axios = require("axios");

async function detectDualRecaptcha(url) {
    const { data: html } = await axios.get(url, { timeout: 15000 });

    const result = {
        hasV3: false,
        hasV2: false,
        v3SiteKey: null,
        v2SiteKey: null,
        dual: false,
    };

    // v3 detection
    const v3Match = html.match(/api\.js\?render=([A-Za-z0-9_-]+)/);
    if (v3Match && v3Match[1] !== "explicit") {
        result.hasV3 = true;
        result.v3SiteKey = v3Match[1];
    }

    // v2 detection
    const v2Match = html.match(/data-sitekey="([^"]+)"/);
    if (v2Match && v2Match[1] !== result.v3SiteKey) {
        result.hasV2 = true;
        result.v2SiteKey = v2Match[1];
    }

    result.dual = result.hasV3 && result.hasV2;
    return result;
}

detectDualRecaptcha("https://staging.example.com/qa-login").then(console.log);

Chiến lược giải reCAPTCHA kép với CaptchaAI

Sau khi xác định trang có cả hai phiên bản, chọn một trong ba cách tiếp cận sau tuỳ mức độ bạn hiểu hành vi của trang đích.

Giải v3 trước, chỉ giải v2 khi bị dự phòng

Cách an toàn nhất, mô phỏng đúng luồng thật của trang: gọi CaptchaAI giải v3, gửi token, rồi kiểm tra phản hồi xem v2 có bị kích hoạt hay không.

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_v3(site_key, page_url, action="login"):
    """Solve reCAPTCHA v3 and return token."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "version": "v3",
        "action": action,
        "json": 1,
    }).json()

    task_id = submit["request"]

    for _ in range(60):
        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("v3 solve timeout")

def solve_v2(site_key, page_url):
    """Solve reCAPTCHA v2 and return token."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "json": 1,
    }).json()

    task_id = submit["request"]

    for _ in range(60):
        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("v2 solve timeout")

def solve_dual_recaptcha(v3_key, v2_key, page_url, action="login"):
    """Handle dual reCAPTCHA: try v3, fall back to v2."""
    # Step 1: Try v3
    v3_token = solve_v3(v3_key, page_url, action)

    # Step 2: Submit v3 token to target
    response = requests.post(f"{page_url}/verify", data={
        "g-recaptcha-response": v3_token,
    })

    # Step 3: Check if v2 fallback is needed
    if "recaptcha" in response.text.lower() and v2_key:
        print("v3 score too low — v2 fallback triggered")
        v2_token = solve_v2(v2_key, page_url)
        return {"version": "v2", "token": v2_token}

    return {"version": "v3", "token": v3_token}

result = solve_dual_recaptcha(
    v3_key="6LcExample_v3_key",
    v2_key="6LcExample_v2_key",
    page_url="https://staging.example.com/qa-login",
)
print(f"Solved with {result['version']}")

Bỏ qua v3 nếu bạn biết chắc kết quả — nếu vài lần chạy đã xác nhận traffic tự động luôn bị đẩy sang v2, bỏ hẳn bước giải v3 để tiết kiệm request:

# If you consistently fail v3 assessment, just solve v2 directly
token = solve_v2(v2_site_key, page_url)
submit_form(token)

Xử lý dự phòng bằng trình duyệt (Selenium)

Với các trang render nhiều JavaScript hoặc ẩn logic điều hướng trong bundle, dùng trình duyệt thật để đọc trạng thái DOM sau khi submit v3 sẽ đáng tin hơn regex trên HTML tĩnh:

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

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

# Check if v2 widget is visible
v2_visible = driver.execute_script("""
    const container = document.querySelector('.g-recaptcha');
    if (!container) return false;
    const style = window.getComputedStyle(container.parentElement);
    return style.display !== 'none' && style.visibility !== 'hidden';
""")

if v2_visible:
    # v2 is showing — solve v2
    sitekey = driver.find_element(
        By.CSS_SELECTOR, "[data-sitekey]"
    ).get_attribute("data-sitekey")
    token = solve_v2(sitekey, driver.current_url)
    driver.execute_script(
        f'document.getElementById("g-recaptcha-response").value = "{token}";'
    )
else:
    # v3 only — solve v3
    # Extract v3 key from page source
    v3_key = driver.execute_script(
        "return document.querySelector('script[src*=\"render=\"]')"
        ".src.match(/render=([^&]+)/)[1];"
    )
    token = solve_v3(v3_key, driver.current_url)
    # Inject v3 token into the form
    driver.execute_script(f"""
        const input = document.createElement('input');
        input.type = 'hidden';
        input.name = 'g-recaptcha-response';
        input.value = '{token}';
        document.querySelector('form').appendChild(input);
    """)

driver.find_element(By.CSS_SELECTOR, "form").submit()

Những cạm bẫy thường gặp khi tự động hóa

Trang dùng reCAPTCHA kép luôn có HAI sitekey khác nhau, và đây là nguồn lỗi phổ biến nhất: key của v3 nằm trong URL script (?render=KEY) và trong grecaptcha.execute('KEY', ...); key của v2 nằm trong data-sitekey="KEY" trên widget. Dùng nhầm key cho sai phiên bản trả về token không hợp lệ — lỗi khó phát hiện vì API vẫn trả token bình thường, chỉ trang đích từ chối.

reCAPTCHA Enterprise kèm dự phòng v2

Một số triển khai Enterprise dùng v3 Enterprise để chấm điểm và vẫn giữ v2 làm thử thách dự phòng — nhận diện qua script recaptcha/enterprise.js:

# Detect and handle Enterprise + v2 combo
if "recaptcha/enterprise.js" in html:
    # Use enterprise parameter for v3
    v3_params = {"enterprise": 1, "version": "v3"}
else:
    v3_params = {"version": "v3"}

Nhiều form trên cùng một trang

Nếu trang có cả form đăng nhập lẫn đăng ký, mỗi form có thể gắn một widget reCAPTCHA riêng. Luôn trích sitekey từ đúng form bạn nhắm tới, không lấy widget đầu tiên tìm thấy trên DOM:

# Target the login form specifically
login_form = soup.find("form", id="login-form")
widget = login_form.find(attrs={"data-sitekey": True})
sitekey = widget["data-sitekey"]

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

Gặp cả v2 và v3 trên một trang thì cần giải bao nhiêu lần?

Thường chỉ một lần. Giải v3 trước — nếu điểm vượt ngưỡng, không có v2 nào xuất hiện. Chỉ khi điểm v3 thấp và trang bật fallback, bạn mới gọi CaptchaAI thêm lần nữa để giải v2.

Giải reCAPTCHA kép qua CaptchaAI tốn bao nhiêu thread?

Mỗi lần giải chiếm một thread, một lượt hoàn chỉnh cần 1–2 thread tuỳ có fallback hay không. Gói BASIC ($15/tháng, 5 thread) chạy được vài luồng song song; nếu bị đẩy sang v2 thường xuyên, cân nhắc ADVANCE ($90/tháng, 50 thread).

Làm sao phát hiện triển khai kép mà không cần mở trình duyệt?

Tải HTML bằng requests hoặc axios rồi quét regex như ví dụ trên: tìm render= cho v3 và data-sitekey cho v2. Chỉ cần Selenium khi trang render sitekey bằng JavaScript phía client.

Token v3 bị từ chối, có phải do nhầm sitekey không?

Rất có thể. Dùng sitekey v2 để giải v3 (hoặc ngược lại), CaptchaAI vẫn trả token bình thường nhưng trang đích từ chối vì sai phiên bản. Kiểm tra: key trong ?render=/execute() là của v3, key trong data-sitekey là của v2.

reCAPTCHA v3 Enterprise kết hợp v2 thì xử lý khác gì không?

Không khác cách gọi CaptchaAI — vẫn gửi method: userrecaptcha với site key tương ứng, chỉ thêm enterprise: 1 khi giải v3 Enterprise. Fallback sang v2 giữ nguyên logic như phần chiến lược.


Tóm tắt

reCAPTCHA kép dùng v3 đánh giá ẩn trước, bật v2 làm thử thách rõ ràng khi điểm quá thấp. Phát hiện bằng cách quét tham số render= (v3) và data-sitekey (v2) trong HTML. Chiến lược tối ưu: giải v3 trước bằng CaptchaAI, gửi token, chỉ giải v2 khi fallback kích hoạt — mỗi phiên bản cần một lệnh gọi API riêng.

Bài viết liên quan

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