В большинстве случаев для решения reCAPTCHA через API CaptchaAI URL-адреса anchor и bframe вообще не нужны — решателю достаточно sitekey и pageurl. Извлекать эти iframe-URL стоит только в узком наборе сценариев: на странице несколько виджетов reCAPTCHA, ключ сайта подгружается динамически, или токен отклоняется из-за несовпадения домена. Ниже — архитектура двух iframe reCAPTCHA (anchor и bframe), формат их параметров и рабочий код извлечения на Python, Node.js и Selenium.
Коротко, когда извлечение вообще пригодится:
- sitekey не виден напрямую в HTML;
- нужно отличить reCAPTCHA v2 от Enterprise;
- CaptchaAI возвращает токен, а форма его не принимает;
- origin staging-домена расходится с тем, под который зарегистрирован ключ.
Архитектура iframe reCAPTCHA: anchor и bframe
reCAPTCHA всегда рендерится в двух вложенных iframe. Anchor отвечает за виджет с чекбоксом «Я не робот» и первичный риск-анализ. bframe появляется только после клика — в нём загружается сетка с картинками, если Google решает показать задачу:
Target page (https://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)
Anchor iframe: чекбокс и первичный риск-анализ
В URL anchor уже на этапе загрузки страницы зашиты ключ сайта, домен и версия скрипта reCAPTCHA:
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
bframe iframe: сетка с картинками
iframe bframe создаётся JavaScript'ом reCAPTCHA динамически — его нет в исходном HTML, пока пользователь не кликнет по чекбоксу и не запустится задача:
https://www.google.com/recaptcha/api2/bframe?
hl=en
&v=jF2Zb_rr_5sv8dMHoGIn-XxY
&k=6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp
Параметры URL anchor
Ниже — полный список параметров, которые встречаются в URL anchor. На практике для отладки чаще всего нужны только два: k (ключ сайта) и co (домен в base64).
| Параметр | Имя | Описание |
|---|---|---|
k |
ключ сайта | site key reCAPTCHA |
co |
закодированный origin | домен в кодировке base64 (протокол + домен + порт) |
v |
версия | хеш версии JS-бандла reCAPTCHA |
hl |
язык | код языка задачи |
size |
размер | normal, compact или invisible |
cb |
callback | уникальный идентификатор функции обратного вызова |
theme |
тема | light или dark |
ar |
aspect ratio | флаг соотношения сторон виджета |
Как декодировать параметр co
Параметр co — это исходный домен в base64. Чтобы прочитать его, достаточно убрать завершающую точку (артефакт padding) и раскодировать строку:
import base64
co_value = "aHR0cHM6Ly9leGFtcGxlLmNvbTo0NDM."
# Remove trailing period (padding artifact)
decoded = base64.b64decode(co_value.rstrip(".") + "==").decode()
print(decoded) # "https://example.com:443"
Так вы получите домен, для которого изначально был сконфигурирован виджет reCAPTCHA — быстрый способ проверить, не привязан ли sitekey к чужому origin.
Совет: если раскодированный
coуказывает на продакшен-домен, а вы тестируете staging, это и есть причина отклонённого токена — сверяйте origin раньше, чем sitekey.
Как извлечь URL anchor и bframe на практике
Три рабочих способа получить эти URL: из статического HTML на Python, тем же способом на Node.js и через Selenium — для страниц, где reCAPTCHA подгружается динамически.
Извлечение из HTML на Python
Если reCAPTCHA рендерится сразу в исходном коде страницы (без ленивой подгрузки), requests + BeautifulSoup вытащат anchor URL, распарсят его параметры, а bframe URL при необходимости соберут вручную из тех же данных:
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
Логика идентична: axios забирает HTML, cheerio парсит DOM, а URL/URLSearchParams разбирают query-параметры anchor:
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
Если reCAPTCHA подгружается через JavaScript уже после первого рендера (частый случай для SPA), статический HTML ничего не даст — нужен настоящий браузер. Selenium ждёт, пока виджет отрисуется, и собирает src всех iframe на странице:
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
Когда URL anchor и bframe реально нужны
Стандартному API CaptchaAI хватает sitekey и pageurl — извлечение iframe нужно только в четырёх ситуациях.
На странице несколько виджетов reCAPTCHA
Типичная причина, по которой sitekey не читается напрямую из HTML:
- сайт держит несколько экземпляров reCAPTCHA одновременно (например, форма логина и форма обратной связи);
- ключ сайта подгружается динамически через JavaScript уже после рендера страницы.
В обоих случаях sitekey всё равно надёжно присутствует в URL anchor:
# 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
Нужно определить версию reCAPTCHA
Путь в URL anchor однозначно показывает, какая версия reCAPTCHA перед вами — обычная v2 или Enterprise:
# 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"
Нужно сверить origin для доменных ограничений
Некоторые интеграции жёстко проверяют домен. Раскодированный co покажет origin, под который реально настроен sitekey, — его можно передать решателю как pageurl:
# Decode the origin from the co parameter
origin = decode_co_parameter(iframes["co"])
# Use this origin as the pageurl for the solver
Токен отклоняется, и непонятно почему
Когда CaptchaAI возвращает валидный токен, а сайт всё равно его не принимает, сравните параметры anchor URL с тем, что вы отправили решателю. Чаще всего расхождение — в sitekey или в домене:
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"]
Типичный кейс: несовпадение origin на staging
На практике это чаще всего всплывает у команд, которые тестируют форму логина на отдельном staging-домене, а sitekey в коде продукта настроен под продакшен origin. Например, если по коду выше вы отправляете pageurl: https://staging.example.com/qa-login, а co в anchor URL раскодируется в https://example.com, решатель вернёт валидный токен reCAPTCHA — но сама форма его отклонит, потому что origin не совпадает с тем, под который зарегистрирован ключ. У распределённых команд из России, Казахстана и других стран СНГ staging и продакшен часто разнесены по разным поддоменам или даже облачным регионам — это одна из самых частых причин «токен получен, а форма не прошла» при первой интеграции CaptchaAI.
Быстрая проверка: сначала сравните домен в
coс доменом, который вы шлёте какpageurl, и только потом ищите причину в другом месте.
Стандартное решение: sitekey + pageurl через CaptchaAI
В большинстве проектов до всего вышеописанного дело не доходит. Отправьте sitekey и pageurl напрямую в CaptchaAI, дождитесь готового токена — и вставьте его в форму:
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
К извлечению anchor/bframe стоит возвращаться, только если стандартный запрос выше не решает проблему и вы уже подозреваете, что дело в sitekey или домене.
Часто задаваемые вопросы
Обязательно ли извлекать anchor и bframe URL для работы с CaptchaAI?
Нет. Решателю нужны только sitekey и pageurl — взаимодействие с anchor и bframe он обрабатывает внутри себя. Извлечение этих URL полезно для отладки, но не для обычного решения.
Почему bframe не виден в исходном HTML страницы?
Причина в том, как reCAPTCHA его создаёт:
- iframe bframe появляется в DOM только после клика по чекбоксу;
- вставляет его сама reCAPTCHA динамически, через JavaScript;
- поймать его получится только реальным браузером — Selenium или Puppeteer, а не обычным HTTP-запросом за HTML.
Можно ли по anchor URL найти лишние или дублирующиеся виджеты reCAPTCHA на странице?
Да, это частый способ диагностики:
- сравните anchor URL всех iframe на странице между собой;
- расхождение в
k(sitekey) сразу выдаёт лишний или устаревший виджет — обычно результат смены темы оформления или незавершённого A/B-теста.
Можно ли передать anchor URL вместо pageurl при отправке в CaptchaAI?
Нет. CaptchaAI ожидает pageurl — адрес страницы, на которой стоит виджет, а не URL самого iframe. Anchor URL нужен только для того, чтобы взять из него sitekey или проверить origin вручную; отправлять его в in.php не нужно.
Кратко
- reCAPTCHA всегда рендерится в двух iframe: anchor (чекбокс) и bframe (задача с картинками);
- URL anchor несёт в себе ключ сайта, закодированный origin и хеш версии;
- для обычного решения через CaptchaAI хватает
sitekeyиpageurl— извлекать URL iframe вручную не нужно; - возвращайтесь к этим техникам только если решение отклоняется из-за домена или на странице несколько виджетов reCAPTCHA с разными ключами.
Похожие статьи
- Как обрабатывать callback reCAPTCHA v2 через API
- Turnstile и reCAPTCHA v2 на одном сайте: как их совместить
- Как устроен механизм callback в reCAPTCHA v2