reCAPTCHA hiển thị qua hai iframe ẩn — anchor (checkbox) và bframe (thử thách hình ảnh) — nhưng phần lớn tích hợp không cần URL của chúng: gửi sitekey và pageurl cho CaptchaAI là đủ (code mẫu ở cuối bài). Trích xuất anchor/bframe chỉ cần khi token bị từ chối vì sai domain, sitekey nạp động không đọc được từ HTML tĩnh, hoặc bạn cần xác minh trang chạy reCAPTCHA v2 hay Enterprise.
Kiến trúc iframe reCAPTCHA: anchor và bframe khác nhau thế nào
reCAPTCHA render bên trong hai iframe lồng nhau. Anchor có sẵn ngay khi trang load; bframe chỉ tạo ra sau khi click checkbox và thử thách được kích hoạt:
Target page (staging.example.com/qa-login)
└── <iframe src="https://www.google.com/recaptcha/api2/anchor?...">
│ ← Anchor iframe: "I'm not a robot" checkbox
│
└── <iframe src="https://www.google.com/recaptcha/api2/bframe?...">
← Bframe iframe: Image challenge grid (loads when clicked)
| Đặc điểm | Anchor iframe | Bframe iframe |
|---|---|---|
| Khi nào tải | Ngay khi load | Sau khi click checkbox |
| Chứa gì | Checkbox + điểm rủi ro | Lưới thử thách hình ảnh |
| Tham số chính | k, co, cb |
k, v, hl |
URL anchor đầy đủ trông như sau:
https://www.google.com/recaptcha/api2/anchor?
ar=1
&k=6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp ← site key
&co=aHR0cHM6Ly9leGFtcGxlLmNvbTo0NDM. ← encoded origin
&hl=en ← language
&v=jF2Zb_rr_5sv8dMHoGIn-XxY ← reCAPTCHA version
&size=normal ← widget size
&cb=89fu2pf0swif ← callback ID
URL bframe gọn hơn, chỉ xuất hiện sau khi checkbox kích hoạt:
https://www.google.com/recaptcha/api2/bframe?
hl=en
&v=jF2Zb_rr_5sv8dMHoGIn-XxY
&k=6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp
Tham số trong URL anchor
Ý nghĩa từng tham số query xuất hiện trong URL anchor:
| Tham số | Ý nghĩa | Mô tả |
|---|---|---|
k |
Sitekey | Sitekey reCAPTCHA |
co |
Origin mã hóa | Base64 của origin (giao thức + domain + port) |
v |
Version | Hash phiên bản JS reCAPTCHA |
hl |
Ngôn ngữ | Mã ngôn ngữ thử thách |
size |
Kích thước | normal, compact hoặc invisible |
cb |
Callback | ID callback duy nhất |
theme |
Giao diện | light hoặc dark |
ar |
Tỷ lệ khung hình | Cờ tỷ lệ khung hình |
Giải mã tham số co
co là origin mã hóa base64 — giải mã ra đúng domain reCAPTCHA tin rằng nó đang chạy:
import base64
co_value = "aHR0cHM6Ly9leGFtcGxlLmNvbTo0NDM."
# Remove trailing period (padding artifact)
decoded = base64.b64decode(co_value.rstrip(".") + "==").decode()
print(decoded) # "https://example.com:443"
Đây là domain gốc cần khớp khi triển khai domain-strict (tình huống 3 bên dưới).
Code trích xuất URL anchor/bframe
Ba cách trích xuất, chọn theo trang static hay render động.
Python: đọc từ HTML tĩnh
Phù hợp khi anchor có sẵn trong response HTML, không cần chạy JavaScript:
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse, parse_qs
import re
import base64
def extract_recaptcha_iframes(url):
"""Extract reCAPTCHA anchor and bframe iframe URLs and parameters."""
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",
}
response = requests.get(url, headers=headers, timeout=15)
soup = BeautifulSoup(response.text, "html.parser")
result = {
"anchor_url": None,
"bframe_url": None,
"site_key": None,
"origin": None,
"version": None,
"language": None,
}
# Find anchor iframe
anchor_iframe = soup.find("iframe", src=re.compile(r"recaptcha.*anchor"))
if anchor_iframe:
anchor_url = anchor_iframe.get("src", "")
result["anchor_url"] = anchor_url
# Parse parameters
parsed = urlparse(anchor_url)
params = parse_qs(parsed.query)
result["site_key"] = params.get("k", [None])[0]
result["version"] = params.get("v", [None])[0]
result["language"] = params.get("hl", [None])[0]
# Decode origin
co = params.get("co", [None])[0]
if co:
try:
padded = co.rstrip(".") + "=="
result["origin"] = base64.b64decode(padded).decode()
except Exception:
result["origin"] = co
# Find bframe iframe (may not be in source — loaded dynamically)
bframe_iframe = soup.find("iframe", src=re.compile(r"recaptcha.*bframe"))
if bframe_iframe:
result["bframe_url"] = bframe_iframe.get("src", "")
# Construct bframe URL from anchor parameters if not found
if not result["bframe_url"] and result["site_key"] and result["version"]:
result["bframe_url"] = (
f"https://www.google.com/recaptcha/api2/bframe?"
f"hl={result['language'] or 'en'}"
f"&v={result['version']}"
f"&k={result['site_key']}"
)
return result
iframes = extract_recaptcha_iframes("https://staging.example.com/qa-login")
print(f"Site key: {iframes['site_key']}")
print(f"Origin: {iframes['origin']}")
print(f"Anchor URL: {iframes['anchor_url']}")
Node.js
Cùng logic, dùng axios và cheerio:
const axios = require("axios");
const cheerio = require("cheerio");
const { URL } = require("url");
async function extractRecaptchaIframes(pageUrl) {
const { data: html } = await axios.get(pageUrl, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
},
timeout: 15000,
});
const $ = cheerio.load(html);
const result = {
anchorUrl: null,
bframeUrl: null,
siteKey: null,
origin: null,
version: null,
};
// Find anchor iframe
const anchorIframe = $("iframe[src*='recaptcha'][src*='anchor']");
if (anchorIframe.length) {
const src = anchorIframe.attr("src");
result.anchorUrl = src;
const url = new URL(src);
result.siteKey = url.searchParams.get("k");
result.version = url.searchParams.get("v");
// Decode origin
const co = url.searchParams.get("co");
if (co) {
try {
result.origin = Buffer.from(
co.replace(/\.$/, ""), "base64"
).toString();
} catch {}
}
}
// Construct bframe URL
if (result.siteKey && result.version) {
result.bframeUrl =
`https://www.google.com/recaptcha/api2/bframe?` +
`hl=en&v=${result.version}&k=${result.siteKey}`;
}
return result;
}
extractRecaptchaIframes("https://staging.example.com/qa-login").then(console.log);
Selenium: khi trang cần render động
Cần khi trang render iframe bằng JavaScript, hoặc cần bframe — dùng trình duyệt thật, chờ rồi đọc src:
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
def extract_iframes_selenium(url):
"""Extract reCAPTCHA iframe URLs from a dynamically loaded page."""
driver = webdriver.Chrome()
driver.get(url)
time.sleep(3) # Wait for reCAPTCHA to load
result = {"anchor_url": None, "bframe_url": None}
# Find all iframes
iframes = driver.find_elements(By.TAG_NAME, "iframe")
for iframe in iframes:
src = iframe.get_attribute("src") or ""
if "recaptcha" in src and "anchor" in src:
result["anchor_url"] = src
elif "recaptcha" in src and "bframe" in src:
result["bframe_url"] = src
driver.quit()
return result
Khi nào trích xuất URL anchor/bframe thực sự đáng làm
Bốn tình huống nên quay lại kỹ thuật trên:
- Sitekey nạp động — không lộ trong HTML tĩnh.
- Phân biệt v2/Enterprise — cần biết chính xác biến thể.
- Domain-strict — backend chỉ nhận token đúng origin.
- Gỡ lỗi token bị từ chối — so khớp anchor với request solver.
1. Sitekey nạp động, không có sẵn trong HTML
Trang render nhiều instance reCAPTCHA hoặc nạp sitekey bằng JavaScript sau khi load — HTML tĩnh sẽ không thấy. URL anchor luôn chứa đúng k= widget đang dùng:
# Extract sitekey from anchor URL when it's not in the page HTML
iframes = extract_recaptcha_iframes(url)
sitekey = iframes["site_key"] # Reliably present in the iframe URL
2. Cần biết chính xác trang chạy reCAPTCHA v2 hay Enterprise
Đường dẫn trong URL anchor tiết lộ biến thể chính xác:
# The anchor URL reveals the exact reCAPTCHA version
if "/api2/anchor" in anchor_url:
recaptcha_type = "v2"
elif "/enterprise/anchor" in anchor_url:
recaptcha_type = "enterprise"
3. Domain-strict: khớp origin cho triển khai giới hạn tên miền
Nếu backend chỉ chấp nhận token đúng domain, giải mã co rồi dùng giá trị đó làm pageurl gửi solver:
# Decode the origin from the co parameter
origin = decode_co_parameter(iframes["co"])
# Use this origin as the pageurl for the solver
4. Gỡ lỗi khi token bị từ chối
Khi token hợp lệ nhưng xác thực server vẫn thất bại, so khớp tham số anchor thực tế với request gửi solver thường lộ ngay chỗ lệch:
def debug_solve_params(anchor_url, solver_pageurl, solver_sitekey):
"""Compare anchor params with solver request to find mismatches."""
parsed = urlparse(anchor_url)
params = parse_qs(parsed.query)
issues = []
# Check sitekey
anchor_key = params.get("k", [None])[0]
if anchor_key != solver_sitekey:
issues.append(f"Sitekey mismatch: anchor={anchor_key}, solver={solver_sitekey}")
# Check origin
co = params.get("co", [None])[0]
if co:
origin = base64.b64decode(co.rstrip(".") + "==").decode()
solver_parsed = urlparse(solver_pageurl)
solver_origin = f"{solver_parsed.scheme}://{solver_parsed.netloc}"
if origin != solver_origin:
issues.append(f"Origin mismatch: anchor={origin}, solver={solver_origin}")
return issues if issues else ["No mismatches found"]
Ví dụ thực tế: một đội QA outsourcing tại TP.HCM chạy ba môi trường song song — staging, UAT, production — mỗi môi trường tự cấu hình sitekey. So khớp k và co trong anchor với request gửi solver giúp đội tìm ra môi trường gán nhầm sitekey trong vài phút.
Giải reCAPTCHA không cần trích xuất iframe
Nếu không cần các kỹ thuật trên, đây là luồng chuẩn: chỉ sitekey và pageurl. Gửi task tới in.php, polling res.php tới khi có token:
import requests
import time
API_KEY = "YOUR_API_KEY"
# All you need: sitekey + pageurl
submit = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": "6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
"pageurl": "https://staging.example.com/qa-login",
"json": 1,
})
task_id = submit.json()["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:
token = result["request"]
print(f"Token: {token[:50]}...")
break
Chỉ khi cách này thất bại — token bị từ chối liên tục hoặc không chắc sitekey nào đúng — mới cần quay lại các kỹ thuật ở trên.
Câu hỏi thường gặp
CaptchaAI có bắt buộc phải có URL anchor hoặc bframe không?
Không. CaptchaAI chỉ cần sitekey và pageurl — solver tự xử lý anchor/bframe nội bộ. Trích xuất hai URL này hữu ích để gỡ lỗi, không phải điều kiện để giải.
Sitekey đọc được từ URL anchor khác với sitekey trong HTML gốc thì dùng cái nào?
Ưu tiên sitekey trong URL anchor (k=). Nếu sitekey nạp bằng JavaScript hoặc trang có nhiều instance reCAPTCHA, giá trị trong HTML tĩnh có thể lỗi thời — anchor luôn đúng.
Làm sao phân biệt reCAPTCHA v2 thường và Enterprise chỉ từ URL?
Nhìn đường dẫn: /api2/anchor là reCAPTCHA v2 tiêu chuẩn, /enterprise/anchor là Enterprise. Tài liệu nội bộ trang thường không ghi rõ, nên đọc URL là cách nhanh nhất.
Tham số co giúp gỡ lỗi domain mismatch thế nào?
Giải mã base64 co ra đúng origin (giao thức + domain + port) mà reCAPTCHA nghĩ nó chạy trên đó. Khác domain bạn đang gửi token tới là nguyên nhân token bị từ chối.
Trích xuất URL anchor/bframe có ảnh hưởng gì đến tốc độ giải của CaptchaAI không?
Không. Bước trích xuất diễn ra phía client, trước khi gửi request tới CaptchaAI — không đổi thời gian xử lý hay tỷ lệ giải. Chỉ dùng khi cần chẩn đoán, không bắt buộc trong luồng giải bình thường.
Tóm tắt
reCAPTCHA dùng hai iframe: anchor (checkbox) và bframe (thử thách hình ảnh), mang sitekey, origin mã hóa và hash version. Để giải với CaptchaAI, bạn chỉ cần sitekey và pageurl. Dùng kỹ thuật trích xuất khi cần gỡ lỗi domain mismatch hoặc lấy sitekey từ trang render động.
Bài viết liên quan
- Giải callback reCAPTCHA v2 qua API
- Xử lý reCAPTCHA v2 và Turnstile trên cùng một site
- Cơ chế callback của reCAPTCHA v2