Hintergründe

reCAPTCHA Anchor- und Bframe-URL extrahieren

Für die Standardlösung mit CaptchaAI brauchen Sie die Anchor- und Bframe-URL gar nicht: sitekey und pageurl genügen, den Rest übernimmt der Solver. Interessant werden die beiden Iframes erst, wenn Sie tiefer eingreifen – etwa um bei einem abgelehnten Token die Ursache zu finden oder den Sitekey aus einer dynamisch geladenen Seite zu ziehen. reCAPTCHA rendert sein Widget in zwei verschachtelten Iframes; beide tragen ihre Konfiguration als kodierte Parameter in der URL. Dieser Artikel zeigt den Aufbau der Iframes, die wichtigsten Parameter und wie Sie die URLs mit Python, Node.js oder Selenium auslesen.

Das Wichtigste vorab:

  • Zum Lösen genügen sitekey und pageurl – die Iframe-URLs sind optional.
  • Zum Debuggen verrät die Anchor-URL Sitekey, kodierten Origin (co) und Versions-Hash (v).
  • Der Bframe entsteht erst per JavaScript nach dem Klick und fehlt daher im ausgelieferten HTML.

So sind die reCAPTCHA-Iframes verschachtelt

Ein reCAPTCHA-Widget besteht aus zwei ineinander liegenden Iframes: dem Anchor-Iframe mit dem Kontrollkästchen „Ich bin kein Roboter" und dem Bframe-Iframe mit dem Raster der Bild-Challenge. Der Bframe wird erst nachgeladen, wenn ein Klick auf das Kontrollkästchen tatsächlich eine Challenge auslöst.

Target page (example.com/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)

Der Anchor-Iframe

Der Anchor-Iframe enthält das Kontrollkästchen und die erste Risikoanalyse. Seine URL trägt die komplette Konfiguration:

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

Der Bframe-Iframe

Der Bframe-Iframe liefert das Raster der Bild-Challenge und wird nur geladen, wenn der Klick auf das Kontrollkästchen eine Challenge nach sich zieht:

https://www.google.com/recaptcha/api2/bframe?
    hl=en
    &v=jF2Zb_rr_5sv8dMHoGIn-XxY
    &k=6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp

Die Parameter der Anchor-URL im Überblick

Die Anchor-URL bündelt die gesamte Widget-Konfiguration in wenigen Query-Parametern:

Parameter Bezeichnung Beschreibung
k Sitekey Der reCAPTCHA-Sitekey
co Kodierter Origin Base64-kodierter Origin (Protokoll + Domain + Port)
v Version Hash der reCAPTCHA-JavaScript-Bundle-Version
hl Sprache Sprachcode der Challenge
size Größe normal, compact oder invisible
cb Callback Eindeutiger Bezeichner der Callback-Funktion
theme Theme light oder dark
ar Seitenverhältnis Flag für das angezeigte Seitenverhältnis

Für die tägliche Praxis zählen davon nur drei:

  • k liefert den Sitekey, den der Solver ohnehin braucht.
  • co deckt Origin-Fehler auf (Protokoll, Domain, Port).
  • v ordnet die Instanz reCAPTCHA v2 oder Enterprise zu.

Hinweis: hl, size, theme und ar steuern nur die Darstellung des Widgets. Für das Lösen mit CaptchaAI sind sie ohne Bedeutung.

Den co-Parameter dekodieren

Der co-Parameter enthält den Base64-kodierten Origin. So machen Sie ihn lesbar:

import base64

co_value = "aHR0cHM6Ly9leGFtcGxlLmNvbTo0NDM."
# Remove trailing period (padding artifact)
decoded = base64.b64decode(co_value.rstrip(".") + "==").decode()
print(decoded)  # "https://example.com:443"

Das Ergebnis zeigt, für welche Origin-Domain das reCAPTCHA konfiguriert wurde – ein zentraler Punkt, sobald Domänenprobleme auftreten.


Anchor- und Bframe-URLs aus einer Seite auslesen

Welchen Weg Sie wählen, hängt davon ab, wie die Zielseite das Widget einbindet:

  • Statisch im HTML? Ein HTTP-Request plus Parser genügt – Python mit BeautifulSoup, Node.js mit cheerio.
  • Dynamisch per JavaScript eingefügt? Nur ein Headless-Browser wie Selenium erreicht die Iframes.

Extraktion mit Python und BeautifulSoup

Für statisch ausgelieferte Seiten reicht ein HTTP-Request plus HTML-Parsing. Die folgende Funktion findet den Anchor-Iframe, liest Sitekey, Version und Sprache aus und dekodiert den Origin. Fehlt der Bframe-Iframe im Quelltext, rekonstruiert sie dessen URL aus den Anchor-Parametern:

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://example.com/login")
print(f"Site key: {iframes['site_key']}")
print(f"Origin: {iframes['origin']}")
print(f"Anchor URL: {iframes['anchor_url']}")

Extraktion mit Node.js

Dasselbe Vorgehen in Node.js mit axios und 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://example.com/login").then(console.log);

Extraktion mit Selenium (dynamische Seiten)

Wird das Widget erst per JavaScript eingefügt, kommen Sie mit reinem HTML-Parsing nicht weiter. Ein Headless-Browser lädt die Seite vollständig und liest die Iframes anschließend aus dem DOM:

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

Wann Sie die Anchor- und Bframe-URLs wirklich brauchen

Die meisten Automatisierungen kommen ohne diese URLs aus: Die CaptchaAI-API verlangt nur sitekey und pageurl. In vier Situationen lohnt sich der Extra-Aufwand aber:

  • Eine Seite bindet mehrere reCAPTCHA-Instanzen ein und Sie müssen den richtigen Sitekey treffen.
  • Sie wollen v2 von Enterprise unterscheiden.
  • Ein domänenstriktes Setup verlangt exakt den Origin aus dem co-Parameter.
  • Token werden abgelehnt und Sie suchen die Ursache.

1. Den richtigen Sitekey bestätigen

Bindet eine Seite mehrere reCAPTCHA-Instanzen ein oder lädt sie den Sitekey dynamisch nach, steht er in der Anchor-URL zuverlässig zur Verfügung:

# 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. Die reCAPTCHA-Version bestimmen

Der Pfad der Anchor-URL verrät, ob es sich um reCAPTCHA v2 oder um die Enterprise-Variante handelt:

# 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. Den Origin für domänenstrikte Setups angleichen

Manche Integrationen prüfen den Origin streng. Der dekodierte co-Wert liefert genau die Domain, die der Solver als pageurl verwenden sollte:

# Decode the origin from the co parameter
origin = decode_co_parameter(iframes["co"])
# Use this origin as the pageurl for the solver

Praxisbeispiel: Läuft Ihre Staging-App etwa bei Hetzner unter einem nicht standardmäßigen Port (https://staging.example.test:8443), wandert dieser Port in den co-Wert. Übergeben Sie dem Solver eine Page-URL ohne Port, weicht der Origin ab – ein häufiger Grund für unerklärliche Ablehnungen.

4. Abgelehnte Token analysieren

Werden Token abgelehnt, deckt ein Abgleich der Anchor-Parameter mit Ihrer Solver-Anfrage die Unstimmigkeit auf:

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"]

Der empfohlene Standardweg: direkt lösen

In den allermeisten Fällen überspringen Sie die Iframe-Extraktion komplett und lösen direkt über CaptchaAI – mit Sitekey und Page-URL:

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://example.com/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

Den Rest übernimmt der Solver im Hintergrund:

  • Er lädt Anchor- und Bframe-Iframe selbst und wertet die Challenge aus.
  • Er hält den wechselnden Versions-Hash (v) aktuell, ohne dass Sie etwas anpassen.
  • Er liefert das fertige g-recaptcha-response-Token zurück.

Die Anchor- und Bframe-Extraktion ist nur nötig, wenn die Standardlösung an Sitekey- oder Domänenproblemen scheitert und eine genauere Analyse verlangt.


Häufige Fragen

Woran erkenne ich an der Anchor-URL, ob eine Seite reCAPTCHA v2 oder Enterprise nutzt?

Am Pfad. /api2/anchor steht für reCAPTCHA v2, /enterprise/anchor für die Enterprise-Variante. Beide lösen Sie mit CaptchaAI über dieselbe Methode userrecaptcha – die Unterscheidung ist vor allem für Ihr eigenes Logging relevant.

Warum wird mein Token trotz korrektem Sitekey abgelehnt?

Meist stimmt der Origin nicht. reCAPTCHA bindet das Token an Protokoll, Domain und Port aus dem co-Parameter. Weicht die pageurl in Ihrer Solver-Anfrage davon ab – etwa http statt https oder ein fehlender Port –, verwirft die Zielseite das Token. Dekodieren Sie co und gleichen Sie beide Werte ab.

Kann ich die Bframe-URL rekonstruieren, ohne einen Browser zu automatisieren?

Ja, sofern Sitekey und Versions-Hash bekannt sind. Der Bframe folgt dem Muster https://www.google.com/recaptcha/api2/bframe?hl=…&v=…&k=…. Fehlt er im HTML, setzen Sie ihn aus den Anchor-Parametern zusammen – die Python-Funktion weiter oben macht genau das. Für eine echte Interaktion mit der Challenge brauchen Sie dagegen Selenium oder Puppeteer.

Ist das Auslesen der reCAPTCHA-Iframe-Parameter unbedenklich?

Die Parameter liegen offen im ausgelieferten HTML und sind technisch frei zugänglich. Rufen Sie dabei Seiten Dritter ab, sollten Sie deren Nutzungsbedingungen prüfen und – sobald personenbezogene Daten im Spiel sind – Ihre datenschutzrechtliche Grundlage (DSGVO) klären. Das ist Sorgfaltspflicht auf Ihrer Seite.

Wie unterscheide ich mehrere reCAPTCHA-Widgets auf derselben Seite?

Über den k-Parameter der jeweiligen Anchor-URL. Jedes Widget rendert einen eigenen Anchor-Iframe mit eigenem Sitekey. Sammeln Sie alle iframe-Elemente, filtern Sie auf anchor und lesen Sie pro Treffer den k-Wert aus.


Fazit

Das Wesentliche in Kürze:

  • reCAPTCHA arbeitet mit zwei Iframes: Anchor (Kontrollkästchen) und Bframe (Bild-Challenge).
  • Die Anchor-URL bündelt Sitekey, kodierten Origin (co) und Versions-Hash (v).
  • Für das reine Lösen mit CaptchaAI genügen sitekey und pageurl – eine Iframe-Extraktion ist nicht nötig.
  • Ihren Wert entfaltet die Extraktion beim Debuggen von Domänenfehlern und beim Auslesen von Sitekeys aus dynamisch geladenen Seiten.

Verwandte Leitfäden

Kommentare sind für diesen Artikel deaktiviert.