单页面应用程序动态加载 reCAPTCHA - 该小部件不存在于初始 HTML 中。React 登录表单仅在组件安装时呈现验证码。Vue 结账页面在用户单击“下订单”后加载 reCAPTCHA。传统的页面源抓取完全错过了这些。以下是如何检测和解决动态加载的 reCAPTCHA。
为什么 SPA 与众不同
| 传统页面 | 温泉 |
|---|---|
| 初始 HTML 中的 reCAPTCHA 脚本 | 路由更改后注入脚本 |
| 小部件在页面加载时呈现 | 小部件在组件安装上呈现 |
| 页面源中的站点密钥 | JavaScript 包中的站点密钥 |
| 表单通过 POST 提交 | 通过 XHR/fetch 提交表单 |
在 SPA 中,reCAPTCHA 小部件可能不存在,直到特定的用户操作触发它。您的自动化必须等待小部件出现。
检测 reCAPTCHA 小部件
等待元素
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://staging.example.com/qa-login")
# SPA may need a click or navigation to trigger reCAPTCHA
page.click("#show-login-form")
# Wait for reCAPTCHA iframe to appear
recaptcha_frame = page.wait_for_selector(
"iframe[src*='recaptcha']",
timeout=15000
)
print("reCAPTCHA detected:", recaptcha_frame.get_attribute("src"))
提取站点密钥
站点密钥可以位于 SPA 中的多个位置:
# Method 1: From the iframe src
iframe_src = recaptcha_frame.get_attribute("src")
# src contains: ...?k=6LcR_RsTAAAAADge...
import re
match = re.search(r'[?&]k=([^&]+)', iframe_src)
site_key = match.group(1) if match else None
# Method 2: From data-sitekey attribute
site_key = page.eval_on_selector(
"[data-sitekey]",
"el => el.getAttribute('data-sitekey')"
)
# Method 3: From JavaScript bundle (last resort)
site_key = page.evaluate("""
() => {
// Check for grecaptcha render calls
const scripts = document.querySelectorAll('script');
for (const s of scripts) {
const match = s.textContent.match(/sitekey['":\s]+(['"])(6L[^'"]+)\\1/);
if (match) return match[2];
}
return null;
}
""")
解决 reCAPTCHA 问题
获得站点密钥后,提交至 CaptchaAI:
import requests
import time
def solve_recaptcha(site_key, page_url):
# Submit task
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"json": 1
})
task_id = resp.json()["request"]
# Poll for result
for _ in range(60):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"]
raise TimeoutError("Solve timed out")
在 SPA 中注入令牌
SPA 不提交传统表格。您需要将token 提交应用程序期望的位置。
方法一:设置隐藏文本区域
token = solve_recaptcha(site_key, "https://staging.example.com/qa-login")
# Inject into the g-recaptcha-response textarea
page.evaluate(f"""
document.getElementById('g-recaptcha-response').value = '{token}';
""")
# Submit the form
page.click("#login-button")
方法二:触发回调
许多 SPA reCAPTCHA 实现都使用回调函数:
# Find the callback function name
callback = page.evaluate("""
() => {
const widget = document.querySelector('.g-recaptcha');
return widget?.getAttribute('data-callback') || null;
}
""")
# Call it with the token
if callback:
page.evaluate(f"window['{callback}']('{token}')")
else:
# Try the default grecaptcha callback
page.evaluate(f"""
if (window.grecaptcha) {{
// Set the response and trigger verification
document.getElementById('g-recaptcha-response').value = '{token}';
// Find and call any registered callbacks
const form = document.querySelector('form');
if (form) form.dispatchEvent(new Event('submit'));
}}
""")
方法3:拦截XHR(JavaScript / Puppeteer)
const puppeteer = require('puppeteer');
async function solveRecaptchaInSPA() {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Intercept the API call and inject the token
await page.setRequestInterception(true);
page.on('request', request => {
if (request.url().includes('/api/login')) {
const postData = JSON.parse(request.postData() || '{}');
// Token already set via page.evaluate — let it pass
request.continue();
} else {
request.continue();
}
});
await page.goto('https://staging.example.com/qa-login');
await page.waitForSelector('[data-sitekey]');
const siteKey = await page.$eval(
'[data-sitekey]',
el => el.getAttribute('data-sitekey')
);
// Solve with CaptchaAI (implementation omitted — same as Python)
const token = await solveCaptcha(siteKey, page.url());
// Inject token and trigger callback
await page.evaluate((t) => {
document.getElementById('g-recaptcha-response').value = t;
const callback = document.querySelector('.g-recaptcha')
?.getAttribute('data-callback');
if (callback && window[callback]) {
window[callback](t);
}
}, token);
}
特定于框架的模式
反应(反应谷歌recaptcha)
React 应用程序通常使用 react-google-recaptcha 包。组件异步渲染:
# Wait for React to mount the component
page.wait_for_selector(".g-recaptcha", state="attached")
# The sitekey is in the rendered div's data attribute
site_key = page.eval_on_selector(
".g-recaptcha", "el => el.dataset.sitekey"
)
Vue(vue-recaptcha)
# Vue may use v-if to conditionally render
# Navigate or interact to trigger the condition
page.click("#proceed-to-checkout")
page.wait_for_selector("iframe[src*='recaptcha']")
角
# Angular apps may lazy-load reCAPTCHA modules
# Wait for the specific Angular component
page.wait_for_selector("re-captcha, app-recaptcha, [data-sitekey]")
处理路线变更
SPA 重用页面——导航无需完全重新加载:
# Listen for reCAPTCHA appearing after SPA navigation
page.goto("https://example.com")
# Navigate within the SPA
page.click("a[href='/login']")
# The URL changed but no page reload happened
# Wait for the CAPTCHA to render in the new "page"
page.wait_for_selector("iframe[src*='recaptcha']", timeout=10000)
故障排除
| 问题 | 原因 | 处理方式 |
|---|---|---|
g-recaptcha-response 文本区域未找到 |
SPA 尚未渲染小部件 | 使用 wait_for_selector 并设置足够的超时时间 |
| 令牌已注入但表单未提交 | 回调未触发 | 查找并调用data-callback函数 |
| 站点密钥不在页面源中 | 捆绑在 JS 中或动态加载 | 等待小部件渲染,然后从 iframe src 中提取 |
| 令牌有效一次然后失败 | SPA重新渲染组件,清算token | 在表单提交之前立即注入令牌 |
| token 提交后小部件重新渲染 | React/Vue 反应性清除该值 | 使用data-callback方法而不是设置textarea |
常问问题
我可以跳过浏览器并仅使用 HTTP 请求解决 SPA reCAPTCHA 问题吗?
如果您可以提取站点密钥并复制表单提交 API 调用,那么可以。许多 SPA 表单通过 REST API 端点提交 - 您可以使用令牌直接调用它。但您需要浏览器首先发现站点密钥和 API 端点。
如何处理仅出现在某些用户操作上的 reCAPTCHA?
自动执行触发操作(单击按钮、填写表单、导航至路线),然后等待 reCAPTCHA 元素出现。使用 wait_for_selector 并设置合理的超时。
CaptchaAI 是否关心 reCAPTCHA 是否在 SPA 中?
不需要。CaptchaAI 只需要站点密钥和页面 URL。小部件在页面上的加载方式不会影响求解。
相关文章
- 如何使用Api解决Recaptcha V2回调
- Recaptcha V2 Turnstile同一站点处理
- Recaptcha V2回调机制
下一步
解决任何 SPA 中动态加载的 reCAPTCHA —获取您的 CaptchaAI API 密钥。