¿Necesitas las URL de los iframes anchor y bframe para resolver un reCAPTCHA? Casi nunca: con CaptchaAI basta el sitekey y la pageurl. Pero si la clave se carga dinámicamente, hay varios widgets en la página o un token se rechaza sin motivo, abrir esos iframes es la vía más rápida para saber qué ocurre. Antes del código, tres ideas:
- El iframe anchor siempre está en el HTML y concentra la site key, el origen y el hash de versión.
- El iframe bframe solo se inyecta cuando la casilla dispara un desafío de imágenes.
- Para resolver no hace falta ninguna de las dos URL: el solver de CaptchaAI las gestiona por dentro.
La arquitectura de iframes anidados de reCAPTCHA
reCAPTCHA se dibuja dentro de dos iframes anidados. El anchor ejecuta el análisis de riesgo; el bframe se superpone solo si ese análisis decide mostrarte un reto.
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)
El iframe anchor: la casilla de verificación
Su URL concentra los datos que importan: site key, origen codificado y hash de versión.
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
El iframe bframe: el desafío de imagen
Repite los identificadores clave del anchor y solo aparece al pulsar la casilla:
https://www.google.com/recaptcha/api2/bframe?
hl=en
&v=jF2Zb_rr_5sv8dMHoGIn-XxY
&k=6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp
Qué parámetros lleva la URL del anchor
Los parámetros de la query string del anchor son:
k— la site key de reCAPTCHA.co— el origen (protocolo + dominio + puerto) en Base64.v— hash de versión del paquete JavaScript.hl— código de idioma del desafío.size—normal,compactoinvisible.cb— identificador de la función de callback.theme—lightodark.ar— indicador de relación de aspecto.
Cómo decodificar el parámetro co
co es el más revelador para depurar: al decodificarlo sabes para qué dominio y puerto se configuró el widget.
import base64
co_value = "aHR0cHM6Ly9leGFtcGxlLmNvbTo0NDM."
# Remove trailing period (padding artifact)
decoded = base64.b64decode(co_value.rstrip(".") + "==").decode()
print(decoded) # "https://example.com:443"
Ese origen es el dato que usaremos para cazar discrepancias de dominio.
Cómo extraer las URL de anchor y bframe
Hay tres formas de obtenerlas: con Python, con Node.js y con Selenium cuando la página las genera al vuelo.
Extracción con Python desde el HTML
Lo más ligero es descargar el HTML y localizar el iframe con BeautifulSoup. Si el bframe no aparece, la función lo reconstruye con la site key y la versión del anchor.
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']}")
Extracción con Node.js
Si tu pipeline vive en JavaScript, la misma idea usa Axios más Cheerio para el HTML y URL para leer la query string.
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);
Extracción con Selenium para páginas dinámicas
Cuando el widget se monta con JavaScript y no aparece en el HTML plano, necesitas un navegador real que espere a que reCAPTCHA cargue y recorra los iframes.
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
Cuándo necesitas de verdad las URL de anchor y bframe
La mayoría de los flujos no tocan estas URL. Un equipo de QA en Ciudad de México que prueba un portal de cita previa con reCAPTCHA nunca abrirá un iframe mientras el sitekey esté en el HTML. La extracción entra en juego en cuatro escenarios:
- Confirmar la site key cuando hay varias instancias o la clave se inyecta dinámicamente.
- Identificar la versión (v2 frente a Enterprise) por la ruta del iframe.
- Alinear el origen decodificando
cocuando el token se ata a un dominio exacto. - Depurar rechazos comparando los parámetros del anchor con lo que enviaste al solver.
En el primero, el anchor es la fuente más fiable de la clave:
# 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
La ruta distingue v2 de 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"
Con dominio estricto, co te da la pageurl correcta para el solver:
# Decode the origin from the co parameter
origin = decode_co_parameter(iframes["co"])
# Use this origin as the pageurl for the solver
Y si un token válido se rechaza, comparar parámetros destapa el desajuste:
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"]
El método recomendado: resolver directo con CaptchaAI
En casi todos los casos, salta la extracción y resuelve directo: envías el sitekey y la pageurl, sondeas el resultado y recibes el 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
Reserva la extracción para cuando la resolución estándar falla por problemas de dominio o de site key.
Preguntas frecuentes
¿Tengo que enviar las URL de anchor o bframe a CaptchaAI?
No. CaptchaAI solo necesita el sitekey y la pageurl; el solver gestiona por dentro la interacción entre anchor y bframe. Extraer esas URL sirve para depurar, no para resolver.
La site key del anchor, ¿es la misma que paso como googlekey?
Sí. El valor de k en la URL del anchor es exactamente el sitekey que envías en googlekey, así que el iframe es buena fuente cuando la clave no está a la vista.
¿Cómo distingo reCAPTCHA v2 de Enterprise mirando el anchor?
Por la ruta de la URL. /recaptcha/api2/anchor es v2 y /recaptcha/enterprise/anchor es Enterprise. Así confirmas la variante antes de enviar la tarea al solver.
Un desajuste de dominio, ¿puede provocar que se rechace mi token?
Sí, es una causa habitual. Decodifica el co del anchor y compáralo con la pageurl que enviaste: si el protocolo, el dominio o el puerto no coinciden, ahí tienes el motivo.
La página tiene varios reCAPTCHA, ¿cuál site key extraigo?
Cada widget monta su propio iframe anchor con su parámetro k. Recorre los iframes cuyo src contenga anchor y quédate con el del formulario a resolver.
En resumen
reCAPTCHA se apoya en dos iframes: el anchor (la casilla) y el bframe (el desafío de imagen), y la URL del anchor concentra la site key, el origen codificado y la versión. Para resolver con CaptchaAI solo necesitas el sitekey y la pageurl. Guárdate la extracción para depurar fallos de dominio o rescatar site keys de páginas dinámicas.
Artículos relacionados
- Cómo resolver el callback de reCAPTCHA v2 con la API
- Manejar reCAPTCHA v2 y Turnstile en el mismo sitio
- El mecanismo de callback de reCAPTCHA v2