故障排查

常见的 reCAPTCHA 隐形错误和修复

不可见的 reCAPTCHA 删除了该复选框,但添加了新的自动化故障模式。最大的问题是根本没有检测到不可见的小部件缺少回调函数提交过期的令牌,以及需要不可见参数时使用v2标准参数

本指南涵盖了每种常见错误模式以及准确的修复方法。如果您需要有关隐形 reCAPTCHA 工作原理的背景知识,请阅读reCAPTCHA Invisible 的工作原理以及解决方法第一的。


快速错误参考

错误 原因 处理方式
ERROR_WRONG_GOOGLEKEY 站点密钥错误或来自不同域的站点密钥 从不可见的小部件 div 或 grecaptcha.render() 调用中提取 sitekey
ERROR_PAGEURL URL 不匹配 — 发送父页面 URL 而不是 iframe URL 使用加载不可见小部件的确切 URL
ERROR_CAPTCHA_UNSOLVABLE 谷歌将该任务标记为不可能完成 使用新的代理和 cookies 重试;检查站点是否切换到 v3
ERROR_BAD_TOKEN_OR_PAGEURL 令牌被目标站点拒绝 验证pageurl完全匹配;通过回调注入,而不是隐藏字段
CAPCHA_NOT_READY 任务仍在处理中 保持每 5 秒轮询一次;隐形解决需要 10-30 秒
ERROR_KEY_DOES_NOT_EXIST CaptchaAI API 密钥无效 检查钥匙位于captchaai.com/account
令牌已接受,但表单失败 token注入后回调未执行 查找并使用令牌调用 data-callback 函数

错误1:未检测到页面上不可见的reCAPTCHA

不可见的 reCAPTCHA 没有可见的复选框。如果您的抓取工具没有检测到它,受验证码保护的请求会因表单提交错误或重定向而默默失败。

如何检测隐形 reCAPTCHA

在 HTML 页面中查找这些模式:

<!-- Pattern 1: div with data-size="invisible" -->
<div class="g-recaptcha" data-sitekey="6LdKlZEU..."
     data-size="invisible"
     data-callback="onSubmit"></div>

<!-- Pattern 2: button with data-sitekey and invisible size -->
<button class="g-recaptcha"
        data-sitekey="6LdKlZEU..."
        data-callback="onSubmit"
        data-action="submit">Submit</button>

<!-- Pattern 3: programmatic render with size: invisible -->
<script>
  grecaptcha.render('submit-btn', {
    sitekey: '6LdKlZEU...',
    callback: onSubmit,
    size: 'invisible'
  });
</script>

检测脚本(Python):

import requests
from bs4 import BeautifulSoup
import re

def detect_invisible_recaptcha(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Check for data-size="invisible"
    widget = soup.find("div", {"data-size": "invisible", "class": "g-recaptcha"})
    if widget:
        return {
            "type": "invisible",
            "sitekey": widget.get("data-sitekey"),
            "callback": widget.get("data-callback")
        }

    # Check for programmatic render with invisible
    scripts = soup.find_all("script")
    for script in scripts:
        if script.string and "size" in str(script.string) and "invisible" in str(script.string):
            key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
            if key_match:
                return {
                    "type": "invisible-programmatic",
                    "sitekey": key_match.group(1),
                    "callback": "check grecaptcha.render() call"
                }

    return None

检测脚本(Node.js):

const axios = require("axios");
const cheerio = require("cheerio");

async function detectInvisibleRecaptcha(url) {
  const { data } = await axios.get(url);
  const $ = cheerio.load(data);

  // Check for data-size="invisible"
  const widget = $(".g-recaptcha[data-size='invisible']");
  if (widget.length) {
    return {
      type: "invisible",
      sitekey: widget.attr("data-sitekey"),
      callback: widget.attr("data-callback"),
    };
  }

  // Check script tags for programmatic invisible render
  const scriptContent = $("script")
    .map((_, el) => $(el).html())
    .get()
    .join("\n");
  if (scriptContent.includes("invisible")) {
    const keyMatch = scriptContent.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
    if (keyMatch) {
      return {
        type: "invisible-programmatic",
        sitekey: keyMatch[1],
        callback: "check grecaptcha.render() call",
      };
    }
  }

  return null;
}

错误 2:站点密钥错误 — ERROR_WRONG_GOOGLEKEY

当您发送的站点密钥与目标页面上不可见的 reCAPTCHA 小部件不匹配时,就会发生这种情况。常见原因:

  • 从不同页面上的v2复选框复制了站点密钥
  • 使用不同 reCAPTCHA 版本的 锚点 URL 中的站点密钥
  • 页面有多个 reCAPTCHA 小部件,而您抓错了一个

修复:提取正确的不可见站点密钥

import requests
from bs4 import BeautifulSoup

def get_invisible_sitekey(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Priority 1: invisible widget
    widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
    if widget:
        return widget["data-sitekey"]

    # Priority 2: any g-recaptcha div (may be invisible without data-size)
    widget = soup.find(class_="g-recaptcha")
    if widget and widget.get("data-sitekey"):
        return widget["data-sitekey"]

    return None

sitekey = get_invisible_sitekey("https://staging.example.com/qa-login")
print(f"Sitekey: {sitekey}")

错误 3:回调未执行 — 表单已提交但没有任何反应

这是开发人员错过的第一个不可见的 reCAPTCHA 失败。与 v2 复选框将token 提交 g-recaptcha-response 就足够了不同,不可见的 reCAPTCHA 几乎总是使用 JavaScript 回调函数。如果您注入令牌但不调用回调,则表单永远不会处理。

回调流程如何工作

  1. grecaptcha.execute() 发起隐形挑战
  2. 解决后Google调用data-callback中指定的函数
  3. 该回调函数提交表单或进行 API 调用

修复:查找并执行回调

第 1 步 — 识别回调名称:

# From HTML: data-callback="onSubmit"
# From JS: callback: onSubmit
# From grecaptcha.render: second argument with callback property

步骤 2 — 注入代币并调用回调 (Selenium):

from selenium import webdriver
import requests
import time

driver = webdriver.Chrome()
driver.get("https://example.com/form")

# Get sitekey
sitekey = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-sitekey")
callback_name = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")

# Solve with CaptchaAI
task_id = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": driver.current_url,
    "invisible": 1
}).text.split("|")[1]

# Poll for result
token = None
for _ in range(60):
    time.sleep(5)
    resp = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY",
        "action": "get",
        "id": task_id
    }).text
    if resp.startswith("OK|"):
        token = resp.split("|")[1]
        break

# Inject token into the response field
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# CRITICAL: Call the callback function
driver.execute_script(f'{callback_name}("{token}");')

第 2 步 - 注入令牌并调用回调(Puppeteer):

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

(async () => {
  const browser = await puppeteer.launch({ headless: "new" });
  const page = await browser.newPage();
  await page.goto("https://example.com/form");

  // Get sitekey and callback
  const { sitekey, callback } = await page.evaluate(() => {
    const el = document.querySelector(".g-recaptcha[data-size='invisible']");
    return {
      sitekey: el?.getAttribute("data-sitekey"),
      callback: el?.getAttribute("data-callback"),
    };
  });

  // Submit to CaptchaAI
  const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: "YOUR_API_KEY",
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: page.url(),
      invisible: 1,
    },
  });
  const taskId = submitResp.data.split("|")[1];

  // Poll for result
  let token;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: "YOUR_API_KEY", action: "get", id: taskId },
    });
    if (result.data.startsWith("OK|")) {
      token = result.data.split("|")[1];
      break;
    }
  }

  // Inject token and fire callback
  await page.evaluate(
    (tok, cb) => {
      document.getElementById("g-recaptcha-response").value = tok;
      if (cb && typeof window[cb] === "function") {
        window[cb](tok);
      }
    },
    token,
    callback,
  );

  await browser.close();
})();

错误4:缺少invisible=1参数

通过 CaptchaAI 解决不可见的 reCAPTCHA 时,您必须在请求中包含 invisible=1。如果没有它,求解器会将任务视为标准 v2 复选框挑战,这可能会导致 ERROR_CAPTCHA_UNSOLVABLE 或目标站点拒绝的令牌。

错误与正确的请求

# WRONG — missing invisible=1
params = {
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url
}

# CORRECT — includes invisible=1
params = {
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "invisible": 1  # Required for invisible reCAPTCHA
}

response = requests.get("https://ocr.captchaai.com/in.php", params=params)

错误5:提交前令牌已过期

不可见的 reCAPTCHA 令牌将在 120 秒后过期 - 与 v2 标准相同。但隐形工作流程通常在解决和提交之间有额外的处理步骤,从而更有可能过期。

症状

  • token 提交后表单返回一般错误
  • 服务器端 siteverify 返回 timeout-or-duplicate
  • 令牌有效,但到达提交步骤所需的时间太长

修复:及时解决

仅当您准备好立即提交时才请求解决:

import requests
import time

def solve_invisible_recaptcha(api_key, sitekey, page_url):
    # Submit task
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "invisible": 1
    })
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")
    task_id = resp.text.split("|")[1]

    # Poll for result
    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
        })
        if result.text.startswith("OK|"):
            return result.text.split("|")[1]
        if result.text != "CAPCHA_NOT_READY":
            raise Exception(f"Solve failed: {result.text}")

    raise Exception("Solve timed out after 5 minutes")

# Usage: solve JUST before you need to submit
# 1. Navigate to page and prepare form data first
# 2. THEN solve the captcha
# 3. Inject token and submit immediately
token = solve_invisible_recaptcha("YOUR_API_KEY", sitekey, page_url)
# Submit within 120 seconds of receiving the token

错误 6:令牌被拒绝 — ERROR_BAD_TOKEN_OR_PAGEURL

目标网站向 Google 验证了令牌,但失败。常见原因:

原因 如何识别 处理方式
错误 pageurl URL 与 sitekey 注册中的域不匹配 使用小部件加载的确切 URL
不同域使用的令牌 跨域令牌复用 使用正确域的 pageurl 解决
令牌已使用 提交同一个令牌两次 为每次提交请求一个新的解决方案
IP 不匹配 您的 IP 与求解器的 IP 不同 添加您的 proxy 参数以匹配会话 IP
隐形旗帜不见了 作为v2标准解决,用于不可见页面 invisible=1 添加到解决请求中

调试清单

def debug_invisible_solve(api_key, sitekey, page_url, proxy=None):
    """Run a diagnostic solve with detailed logging."""
    print(f"Sitekey: {sitekey}")
    print(f"Page URL: {page_url}")
    print(f"Proxy: {proxy or 'none'}")

    params = {
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "invisible": 1
    }
    if proxy:
        params["proxy"] = proxy
        params["proxytype"] = "HTTP"

    # Submit
    resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
    print(f"Submit response: {resp.text}")
    if not resp.text.startswith("OK|"):
        return None

    task_id = resp.text.split("|")[1]
    print(f"Task ID: {task_id}")

    # Poll with timing
    start = time.time()
    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
        })
        elapsed = time.time() - start
        print(f"  [{elapsed:.0f}s] {result.text[:50]}")
        if result.text.startswith("OK|"):
            token = result.text.split("|")[1]
            print(f"Token received after {elapsed:.0f}s")
            print(f"Token length: {len(token)} characters")
            print(f"Token starts with: {token[:30]}...")
            return token
        if result.text != "CAPCHA_NOT_READY":
            print(f"FAILED: {result.text}")
            return None

    print("TIMEOUT after 5 minutes")
    return None

错误 7:一页上有多个 reCAPTCHA 小部件

某些页面同时具有可见的 v2 复选框和不可见的 reCAPTCHA。如果您解决了错误的问题,则令牌有效,但与保护您所需操作的小部件不匹配。

修复:定位正确的小部件

from bs4 import BeautifulSoup

def find_all_recaptcha_widgets(html):
    soup = BeautifulSoup(html, "html.parser")
    widgets = []

    for el in soup.find_all(class_="g-recaptcha"):
        widgets.append({
            "sitekey": el.get("data-sitekey"),
            "size": el.get("data-size", "normal"),
            "callback": el.get("data-callback"),
            "tag": el.name,
            "id": el.get("id")
        })

    return widgets

# Example output:
# [
#   {"sitekey": "6LdA...", "size": "normal", "callback": None, "tag": "div", "id": "recaptcha-login"},
#   {"sitekey": "6LdB...", "size": "invisible", "callback": "onRegister", "tag": "div", "id": "recaptcha-register"}
# ]
# Use the widget with size="invisible" for the invisible solve

具有错误处理功能的完整隐形 reCAPTCHA 求解器

这个生产就绪的包装器可以处理上述所有错误:

import requests
import time
import logging

logger = logging.getLogger(__name__)

class InvisibleRecaptchaSolver:
    def __init__(self, api_key, max_retries=3):
        self.api_key = api_key
        self.max_retries = max_retries
        self.base_url = "https://ocr.captchaai.com"

    def solve(self, sitekey, page_url, proxy=None):
        """Solve invisible reCAPTCHA with automatic retry on transient errors."""
        for attempt in range(1, self.max_retries + 1):
            try:
                token = self._attempt_solve(sitekey, page_url, proxy)
                if token:
                    return token
            except Exception as e:
                logger.warning(f"Attempt {attempt} failed: {e}")
                if attempt < self.max_retries:
                    time.sleep(2 ** attempt)
        raise Exception(f"Failed to solve after {self.max_retries} attempts")

    def _attempt_solve(self, sitekey, page_url, proxy):
        params = {
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url,
            "invisible": 1
        }
        if proxy:
            params["proxy"] = proxy
            params["proxytype"] = "HTTP"

        # Submit task
        resp = requests.get(f"{self.base_url}/in.php", params=params)

        if "ERROR" in resp.text:
            error = resp.text.strip()
            if error in ("ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"):
                raise Exception(f"Configuration error (do not retry): {error}")
            if error == "ERROR_ZERO_BALANCE":
                raise Exception("Account balance is zero — add funds")
            raise Exception(f"Submit error: {error}")

        if not resp.text.startswith("OK|"):
            raise Exception(f"Unexpected submit response: {resp.text}")

        task_id = resp.text.split("|")[1]

        # Poll for result
        for _ in range(60):
            time.sleep(5)
            result = requests.get(f"{self.base_url}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id
            })

            if result.text.startswith("OK|"):
                return result.text.split("|")[1]

            if result.text == "CAPCHA_NOT_READY":
                continue

            if result.text == "ERROR_CAPTCHA_UNSOLVABLE":
                logger.warning("Captcha unsolvable — will retry with new task")
                return None

            raise Exception(f"Poll error: {result.text}")

        raise Exception("Solve timed out after 5 minutes")


# Usage
solver = InvisibleRecaptchaSolver("YOUR_API_KEY")
token = solver.solve(
    sitekey="6LdKlZEU...",
    page_url="https://staging.example.com/qa-login"
)
print(f"Token: {token[:50]}...")
const axios = require("axios");

class InvisibleRecaptchaSolver {
  constructor(apiKey, maxRetries = 3) {
    this.apiKey = apiKey;
    this.maxRetries = maxRetries;
    this.baseUrl = "https://ocr.captchaai.com";
  }

  async solve(sitekey, pageUrl, proxy) {
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        const token = await this._attemptSolve(sitekey, pageUrl, proxy);
        if (token) return token;
      } catch (err) {
        console.warn(`Attempt ${attempt} failed: ${err.message}`);
        if (attempt < this.maxRetries) {
          await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
        }
      }
    }
    throw new Error(`Failed to solve after ${this.maxRetries} attempts`);
  }

  async _attemptSolve(sitekey, pageUrl, proxy) {
    const params = {
      key: this.apiKey,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageUrl,
      invisible: 1,
    };
    if (proxy) {
      params.proxy = proxy;
      params.proxytype = "HTTP";
    }

    // Submit task
    const submitResp = await axios.get(`${this.baseUrl}/in.php`, { params });
    if (submitResp.data.includes("ERROR")) {
      const error = submitResp.data.trim();
      if (["ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"].includes(error)) {
        throw new Error(`Configuration error (do not retry): ${error}`);
      }
      throw new Error(`Submit error: ${error}`);
    }

    const taskId = submitResp.data.split("|")[1];

    // Poll for result
    for (let i = 0; i < 60; i++) {
      await new Promise((r) => setTimeout(r, 5000));
      const result = await axios.get(`${this.baseUrl}/res.php`, {
        params: { key: this.apiKey, action: "get", id: taskId },
      });

      if (result.data.startsWith("OK|")) {
        return result.data.split("|")[1];
      }
      if (result.data === "CAPCHA_NOT_READY") continue;
      if (result.data === "ERROR_CAPTCHA_UNSOLVABLE") return null;
      throw new Error(`Poll error: ${result.data}`);
    }
    throw new Error("Solve timed out after 5 minutes");
  }
}

// Usage
const solver = new InvisibleRecaptchaSolver("YOUR_API_KEY");
solver.solve("6LdKlZEU...", "https://staging.example.com/qa-login").then((token) => {
  console.log(`Token: ${token.substring(0, 50)}...`);
});

故障排除清单

当隐形 reCAPTCHA 解决失败时,请运行此检查表:

查看 命令/Action
1 确认它是不可见的,不是v2标准 在渲染调用中查找 data-size="invisible"size: 'invisible'
2 验证 sitekey 是否正确 具体与隐形小部件上的data-sitekey进行比较
3 确认 API 请求中的 invisible=1 检查您的 in.php 参数
4 检查 pageurl 是否完全匹配 使用浏览器 DevTools URL,而不是重定向 URL
5 找到回调函数名 grecaptcha.render() 中查找 data-callback 属性或 callback
6 验证token注入+回调调用 这两个步骤都是必需的——仅凭令牌是不够的
7 检查令牌新鲜度 令牌必须在 120 秒内使用
8 使用代理测试 IP 是否重要 添加proxyproxytype参数

常问问题

隐形 reCAPTCHA 与解决问题的 v2 标准有何不同?

API 方法相同 (method=userrecaptcha),但您必须将 invisible=1 添加到您的请求中。关键区别在于注入方面:不可见的 reCAPTCHA 几乎总是需要在注入令牌后调用 JavaScript 回调函数,而 v2 标准通常仅适用于隐藏字段。

为什么我的代币在测试中有效但在生产中失败?

很可能是 IP 不匹配。在测试中,解算器和您的浏览器可能共享相似的 IP。在生产中,求解器的 IP 和服务器的 IP 不同。添加与您的会话 IP 匹配的代理参数来解决此问题。

隐形 reCAPTCHA 需要多长时间才能解决?

通过 CaptchaAI,典型的求解时间为 10-30 秒。隐形挑战通常比 v2 复选框挑战更快,因为它们不需要图像识别 - 它们依赖于风险分析。

我可以在没有浏览器的情况下解决隐形 reCAPTCHA 问题吗?

是的。由于解决是通过 API 在服务器端进行的,因此您只需要 sitekey 和 pageurl。仅当您必须在实际页面上执行回调函数时才需要浏览器。对于纯 API 工作流程,提取站点密钥,通过 CaptchaAI 进行解析,然后通过 HTTP 请求提交令牌。


后续步骤

相关文章

该文章已禁用评论。