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
- Xử lý reCAPTCHA trên Single Page Application tải động
- Giải reCAPTCHA v2 Callback bằng API
- Xử lý reCAPTCHA v2 và Turnstile trên cùng một site