Explainers

Руководство по извлечению якорей reCAPTCHA и URL-адресов Bframe

В большинстве случаев для решения 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 с разными ключами.

Похожие статьи

Комментарии для этой статьи отключены.