Tutorials

Iframe 验证码提取:解决嵌套框架中的验证码

验证码卡在第二层、第三层 iframe 里,怎么都提取不到 sitekey?多数情况不是代码写错了,而是没先摸清 iframe 的嵌套结构就直接动手。本文讲清楚 reCAPTCHA v2 和 Cloudflare Turnstile 在支付页、弹窗等多层 iframe 场景下的处理方法,附 Python(Selenium)和 JavaScript(Puppeteer)两套可直接运行的代码。整体思路分三步:

  1. 定位——摸清验证码在第几层 iframe。
  2. 提取——从 src/data-sitekey 拿到 sitekey。
  3. 注入——把 token 写回对应 frame。

嵌套 iframe 验证码是什么结构?

典型的嵌套结构长这样:

Main page
  └── iframe#payment-frame (cross-origin)
        └── iframe[src*="recaptcha/api2/anchor"] (Google-hosted)
              └── reCAPTCHA widget

sitekey 不在最里层,而是在外层 iframe——也就是真正渲染验证码小部件的那一层。最里面那个 Google 的 iframe 只是视觉挑战本身,你不需要直接与它交互。你要找的是外层 iframe 的 src 属性里的 k= 参数,或者它的 data-sitekey 属性。

一个常见场景:跨境电商结账页里,Stripe、PayPal 之类的支付组件本身就是 iframe,验证码小部件又嵌套在支付 iframe 内部——iframe#payment-frame 的命名就来自这类场景。

小贴士:层级不确定时,先在开发者工具里画一遍 iframe 树,再动手写提取逻辑。


常见错误与排查方法

错误类型 会出现什么症状 解决办法
用主页面 URL 当 pageurl token 提交后被网站拒绝 改用渲染验证码小部件那一层 iframe 的 URL
注入 token 后没切回父框架 后续操作全部失败 注入完成后调用 switch_to.parent_frame()switch_to.default_content()
从错误的 iframe 里取 sitekey 报错 ERROR_WRONG_GOOGLEKEY sitekey 在包含 .g-recaptcha 的那层 iframe 上,不是内层 Google 挑战 iframe
跨域 iframe 里执行 JS 报错 控制台出现 SecurityError 你无法在跨源 iframe 内直接用 execute_script——从 src 属性里正则提取 sitekey 即可

调试顺序:先按上表定位错误类型,再对照下方 FAQ 逐条核对,比盲目加日志更快。


完整实现:用 Selenium 和 Puppeteer 拿到 token(含多层嵌套)

Python + Selenium:切换 iframe 提取 sitekey 并解决 reCAPTCHA

下面的代码演示完整流程:切换进外层 iframe、定位 reCAPTCHA iframe 并提取 sitekey、提交给 CaptchaAI、轮询拿到 token,再把 token 注回原来的框架上下文。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
import re
import time

API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example.com/checkout")

# Step 1: Switch into the outer iframe
wait = WebDriverWait(driver, 15)
outer_iframe = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "iframe#payment-frame"))
)
driver.switch_to.frame(outer_iframe)

# Step 2: Find the reCAPTCHA iframe and extract sitekey
recaptcha_iframe = wait.until(
    EC.presence_of_element_located(
        (By.CSS_SELECTOR, 'iframe[src*="recaptcha/api2/anchor"]')
    )
)
src = recaptcha_iframe.get_attribute("src")
sitekey = re.search(r"k=([A-Za-z0-9_-]+)", src).group(1)
page_url = driver.current_url
print(f"Sitekey: {sitekey}")
print(f"Page URL: {page_url}")

# Step 3: Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "json": "1"
})
task_id = resp.json()["request"]

token = None
for _ in range(24):
    time.sleep(5)
    poll = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": "1"
    }).json()
    if poll["status"] == 1:
        token = poll["request"]
        break
    if poll["request"] != "CAPCHA_NOT_READY":
        raise Exception(poll["request"])

print(f"Token: {token[:50]}...")

# Step 4: Inject token (still inside the outer iframe)
driver.execute_script("""
    document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];
    if (typeof grecaptcha !== 'undefined') {
        grecaptcha.getResponse = function() { return arguments[0]; };
    }
""", token)

# Step 5: Switch back to main page
driver.switch_to.default_content()
print("Token injected, switched back to main frame")

预期输出:

Sitekey: 6Le-SITEKEY-abc123
Page URL: https://example.com/checkout
Token: 03AGdBq26ZfPxL...
Token injected, switched back to main frame

JavaScript + Puppeteer:无需手动切换 frame

Puppeteer 对多层 frame 的支持是原生的,不用像 Selenium 那样一层层显式切换——直接拿到 contentFrame() 就能在对应上下文里查找元素、执行脚本。

const puppeteer = require('puppeteer');
const axios = require('axios');

const API_KEY = 'YOUR_API_KEY';

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto('https://example.com/checkout', {
    waitUntil: 'networkidle2'
  });

  // Step 1: Find the payment iframe
  const paymentFrameHandle = await page.waitForSelector('iframe#payment-frame');
  const paymentFrame = await paymentFrameHandle.contentFrame();

  // Step 2: Find reCAPTCHA iframe inside the payment frame
  const recaptchaFrameHandle = await paymentFrame.waitForSelector(
    'iframe[src*="recaptcha/api2/anchor"]'
  );
  const src = await paymentFrame.evaluate(
    el => el.getAttribute('src'), recaptchaFrameHandle
  );

  const sitekey = src.match(/k=([A-Za-z0-9_-]+)/)[1];
  const pageUrl = page.url();
  console.log(`Sitekey: ${sitekey}`);

  // Step 3: Solve with CaptchaAI
  const submitResp = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: {
      key: API_KEY,
      method: 'userrecaptcha',
      googlekey: sitekey,
      pageurl: pageUrl,
      json: 1
    }
  });
  const taskId = submitResp.data.request;

  let token = null;
  for (let i = 0; i < 24; i++) {
    await new Promise(r => setTimeout(r, 5000));
    const pollResp = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
    });
    if (pollResp.data.status === 1) {
      token = pollResp.data.request;
      break;
    }
    if (pollResp.data.request !== 'CAPCHA_NOT_READY') {
      throw new Error(pollResp.data.request);
    }
  }

  console.log(`Token: ${token.substring(0, 50)}...`);

  // Step 4: Inject token into the payment frame
  await paymentFrame.evaluate((tkn) => {
    const textarea = document.querySelector(
      'textarea[name="g-recaptcha-response"]'
    );
    textarea.value = tkn;

    const callback = document.querySelector('.g-recaptcha')
      ?.getAttribute('data-callback');
    if (callback && typeof window[callback] === 'function') {
      window[callback](tkn);
    }
  }, token);

  console.log('Token injected into payment iframe');
})();

三层以上嵌套:链式切换 iframe

遇到三层甚至更深的嵌套,思路不变,只是切换动作要一层层链下去,直到进入渲染验证码的那一层再提取 sitekey。

Selenium(链式切换)

# Main → iframe-1 → iframe-2 → CAPTCHA
driver.switch_to.frame(driver.find_element(By.ID, "iframe-1"))
driver.switch_to.frame(driver.find_element(By.ID, "iframe-2"))
# Now extract sitekey from this context

# To go back up one level:
driver.switch_to.parent_frame()

# To go back to main:
driver.switch_to.default_content()

Puppeteer(链式切换)

const frame1 = await (await page.$('iframe#iframe-1')).contentFrame();
const frame2 = await (await frame1.$('iframe#iframe-2')).contentFrame();
// Extract sitekey from frame2

常见问题

怎么快速确认验证码嵌套了几层 iframe?

  1. 打开开发者工具的 Elements 面板,逐层展开 <iframe> 标签。
  2. 找到包含 .g-recaptcha.cf-turnstile 的那一层,就是目标框架。
  3. 中间跨了多少层都无所谓,判断方式不变。

跨域的 iframe,还能提取到 sitekey 吗?

可以。sitekey 在 iframe 元素的 src 属性或 data-sitekey 属性里,这两个属性从父框架就能直接读到,不受同源策略限制。你完全不需要在跨源 iframe 内部执行 JavaScript 才能拿到它。

pageurl 该填父页面地址,还是 iframe 自己的地址?

  1. 默认填加载验证码小部件那个页面的地址,通常是 iframe 自己的 URL,不是顶级页面。
  2. 拿不准时,切换进对应 frame 后打印 driver.current_url 确认。

token 提交进去了,页面却没反应,是哪里出了问题?

先检查 token 是否写进了正确的框架——必须是包含 textarea[name="g-recaptcha-response"] 的那一层,而不是主页面或某个中间层 iframe。其次确认注入后有没有触发站点自己的 data-callback 回调,很多站点靠这个回调才会真正提交表单。

CaptchaAI 能处理嵌套 iframe 里的 Cloudflare Turnstile 吗?

  1. 定位渲染 Turnstile 小部件的那层 iframe,拿到 sitekey。
  2. 提交给 CaptchaAI 解决,拿到 token。
  3. 写入 cf-turnstile-response 字段即可,不需要额外处理。

立即用 CaptchaAI 打通任意深度的 iframe 验证码

前往 captchaai.com 注册账号并获取 API Key,几分钟内就能把上面的代码接入你自己的项目。


相关指南

该文章已禁用评论。