在 Vibium 中解决 CAPTCHA

当 AI 代理自动化浏览器完成现实世界任务时,CAPTCHAs 是最大的障碍。受保护的页面会阻止代理,表单无法提交,整个工作流程会因等待人工干预而停滞。

Vibium 是一种下一代浏览器自动化工具,专为 AI 代理和人类用户设计。它基于 WebDriver BiDi 协议,由 Selenium 和 Appium 的开发者创建,为代理提供了一种快速、基于标准的方式控制浏览器。但像任何自动化工具一样,它也会被 CAPTCHAs 阻止。

这里有个问题:Vibium 在其 Go 启动器中硬编码了 --disable-extensions 你无法传递自定义的 Chrome 标志。这意味着 Playwright 和 Puppeteer 等工具使用的 Chrome 扩展方法在此处不起作用。

CapSolver 通过不同的策略解决了这个问题。它不依赖浏览器扩展,而是直接调用 CapSolver REST API 来解决 CAPTCHA,然后使用 Vibium 的 JavaScript 评估功能将生成的令牌注入页面。这种基于 API 的方法提供了完全的控制,并与 Vibium 的架构完美兼容。


什么是 Vibium?

Vibium 是一个专为 AI 代理和人工操作员设计的浏览器自动化平台。它作为一个单独的 Go 二进制文件提供,安装时无需配置,并使用现代的 WebDriver BiDi 协议与浏览器进行快速、双向通信。

关键功能

  • WebDriver BiDi 协议:基于标准的双向浏览器通信(非 CDP)
  • MCP 服务器:内置的模型上下文协议服务器,使 AI 代理可以原生控制浏览器
  • 语义元素查找:通过语义定位元素,而不仅仅是 CSS 选择器
  • 多语言 SDK:支持 JavaScript/TypeScript、Python 和 Java 的客户端库
  • 单个 Go 二进制文件:零依赖、零配置,只需下载并运行
  • 由 Selenium/Appium 开发者创建:在浏览器自动化标准方面具有深厚的专业知识

AI 代理使用场景

Vibium 的 MCP 服务器允许 AI 代理通过标准化协议发出浏览器命令。代理可以:

  • 导航到 URL 并与页面交互
  • 通过语义定位元素(“登录按钮”而不是 #btn-login
  • 通过 browser_evaluate 在页面上评估任意 JavaScript
  • 填写表单、点击按钮、提取内容
  • 管理多个浏览器会话

可以将其视为给你的 AI 代理一个它可以用自然语言交谈的浏览器。


什么是 CapSolver?

CapSolver 是一个领先的 CAPTCHA 解决服务,提供 AI 驱动的解决方案来绕过各种 CAPTCHA 挑战。它支持多种 CAPTCHA 类型,并具有快速的响应时间,可无缝集成到自动化工作流中。

支持的 CAPTCHA 类型


为什么这个集成不同

大多数浏览器自动化工具(Playwright、Puppeteer、OpenClaw、NanoClaw)通过直接在浏览器中加载 CapSolver Chrome 扩展来解决 CAPTCHAs。扩展会自动检测 CAPTCHAs,在后台解决它们,并无形地注入令牌。

Vibium 不能使用这种方法。 Go 启动器在启动 Chrome 时硬编码了 --disable-extensions。没有配置选项或变通方法来加载扩展。

相反,集成直接使用 CapSolver REST API

扩展方法(Playwright 等) API 方法(Vibium)
工作原理 扩展自动检测并无声解决 CAPTCHAs 你的代码调用 API,获取令牌,注入它
需要扩展 是(通过 --load-extension 加载 Chrome 扩展) 否(纯 HTTP API 调用)
代理意识 代理不需要知道 CAPTCHAs 代理(或脚本)主动管理解决流程
Chrome 标志 需要 --load-extension 支持 与任何 Chrome 标志兼容,包括 --disable-extensions
控制 自动,不透明 明确,对每个步骤有完全控制
灵活性 仅限于扩展支持的内容 可自定义检测、重试逻辑、按站点注入令牌
最适合 允许自定义 Chrome 参数的工具 像 Vibium 这样限制 Chrome 参数的工具

关键洞察:API 方法实际上更强大。你控制何时检测、何时解决,以及如何精确注入令牌。它与任何浏览器自动化工具兼容,无论其 Chrome 标志限制如何。


前提条件

在设置集成之前,请确保您已:

  1. 安装 Vibium (从 GitHub 下载)
  2. 拥有 CapSolver 账户和 API 密钥 (在此注册)
  3. 任选其一:Node.js 18+ / Python 3.8+ / Java 17+

安装 Vibium

# macOS / Linux — 单个二进制文件,零依赖
curl -fsSL https://vibium.dev/install.sh | bash

# 或直接从 GitHub 发布页面下载
# https://github.com/VibiumDev/vibium/releases

验证安装:

vibium --version

测试无需 Chrome

Vibium 管理自己的浏览器生命周期。你不需要安装 Chrome for Testing、Playwright 的内置 Chromium 或任何特殊浏览器变体。Vibium 内部处理浏览器的下载和管理。


分步设置

第 1 步:获取 CapSolver API 密钥

  1. capsolver.com 注册
  2. 导航到你的仪表板
  3. 复制你的 API 密钥(以 CAP- 开头)

将其设置为环境变量:

export CAPSOLVER_API_KEY="CAP-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

第 2 步:安装 Vibium SDK 和 HTTP 客户端

JavaScript:

npm install vibium

Python:

pip install vibium requests

Java (Gradle):

implementation 'com.vibium:vibium:26.3.18'

第 3 步:创建 CAPTCHA 检测辅助工具

在解决 CAPTCHA 之前,你需要知道它的类型并提取站点密钥。使用 Vibium 的 browser_evaluate 检查页面。

检测 JavaScript 在所有三种语言中都相同——只有主机调用不同:

JavaScript:

const { browser } = require('vibium/sync')

function detectCaptcha(page) {
  return page.evaluate(`(() => {
    const v2 = document.querySelector('.g-recaptcha');
    if (v2) return { type: 'recaptcha-v2', siteKey: v2.getAttribute('data-sitekey') };

    for (const s of document.querySelectorAll('script[src*="recaptcha/api.js"]')) {
      const m = s.src.match(/render=([^&]+)/);
      if (m && m[1] !== 'explicit') return { type: 'recaptcha-v3', siteKey: m[1] };
    }

    const t = document.querySelector('.cf-turnstile');
    if (t) return { type: 'turnstile', siteKey: t.getAttribute('data-sitekey') };

    return { type: 'none', siteKey: null };
  })()`)
}

Python:

from vibium import browser

def detect_captcha(page) -> dict:
    return page.evaluate("""(() => {
        const v2 = document.querySelector('.g-recaptcha');
        if (v2) return { type: 'recaptcha-v2', siteKey: v2.getAttribute('data-sitekey') };

        for (const s of document.querySelectorAll('script[src*="recaptcha/api.js"]')) {
            const m = s.src.match(/render=([^&]+)/);
            if (m && m[1] !== 'explicit') return { type: 'recaptcha-v3', siteKey: m[1] };
        }

        const t = document.querySelector('.cf-turnstile');
        if (t) return { type: 'turnstile', siteKey: t.getAttribute('data-sitekey') };

        return { type: 'none', siteKey: null };
    })()""")

Java:

var result = page.evaluate("""
    (() => {
        const v2 = document.querySelector('.g-recaptcha');
        if (v2) return { type: 'recaptcha-v2', siteKey: v2.getAttribute('data-sitekey') };

        for (const s of document.querySelectorAll('script[src*="recaptcha/api.js"]')) {
            const m = s.src.match(/render=([^&]+)/);
            if (m && m[1] !== 'explicit') return { type: 'recaptcha-v3', siteKey: m[1] };
        }

        const t = document.querySelector('.cf-turnstile');
        if (t) return { type: 'turnstile', siteKey: t.getAttribute('data-sitekey') };

        return { type: 'none', siteKey: null };
    })()
    """);
String captchaType = (String) ((Map) result).get("type");
String siteKey = (String) ((Map) result).get("siteKey");

第 4 步:创建 CAPTCHA 解决函数

调用 CapSolver API 创建任务,然后轮询结果。

JavaScript:

const CAPSOLVER_API = 'https://api.capsolver.com'
const API_KEY = process.env.CAPSOLVER_API_KEY

async function createTask(taskData) {
  const res = await fetch(`${CAPSOLVER_API}/createTask`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ clientKey: API_KEY, task: taskData }),
  })
  const data = await res.json()
  if (data.errorId !== 0) throw new Error(`CapSolver: ${data.errorDescription}`)
  return data.taskId
}

async function getTaskResult(taskId, maxAttempts = 60) {
  for (let i = 0; i < maxAttempts; i++) {
    await new Promise(r => setTimeout(r, 2000))
    const res = await fetch(`${CAPSOLVER_API}/getTaskResult`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ clientKey: API_KEY, taskId }),
    })
    const data = await res.json()
    if (data.status === 'ready') return data
    if (data.status === 'failed') throw new Error(`Failed: ${data.errorDescription}`)
  }
  throw new Error('Timeout')
}

async function solveCaptcha(captchaInfo, pageUrl) {
  const taskTypes = {
    'recaptcha-v2': { type: 'ReCaptchaV2TaskProxyLess', websiteURL: pageUrl, websiteKey: captchaInfo.siteKey },
    'recaptcha-v3': { type: 'ReCaptchaV3TaskProxyLess', websiteURL: pageUrl, websiteKey: captchaInfo.siteKey, pageAction: 'submit' },
    'turnstile':    { type: 'AntiTurnstileTaskProxyLess', websiteURL: pageUrl, websiteKey: captchaInfo.siteKey },
  }
  const taskData = taskTypes[captchaInfo.type]
  if (!taskData) throw new Error(`Unsupported: ${captchaInfo.type}`)

  const taskId = await createTask(taskData)
  const result = await getTaskResult(taskId)
  return result.solution.gRecaptchaResponse || result.solution.token || ''
}

Python:

import os, time, requests

CAPSOLVER_API = "https://api.capsolver.com"
API_KEY = os.environ["CAPSOLVER_API_KEY"]

def create_task(task_data: dict) -> str:
    data = requests.post(f"{CAPSOLVER_API}/createTask",
        json={"clientKey": API_KEY, "task": task_data}).json()
    if data.get("errorId", 0) != 0:
        raise Exception(f"CapSolver: {data.get('errorDescription')}")
    return data["taskId"]

def get_task_result(task_id: str, max_attempts: int = 60) -> dict:
    for _ in range(max_attempts):
        time.sleep(2)
        data = requests.post(f"{CAPSOLVER_API}/getTaskResult",
            json={"clientKey": API_KEY, "taskId": task_id}).json()
        if data["status"] == "ready":
            return data
        if data["status"] == "failed":
            raise Exception(f"Failed: {data.get('errorDescription')}")
    raise Exception("Timeout")

def solve_captcha(captcha_info: dict, page_url: str) -> str:
    task_types = {
        "recaptcha-v2": {"type": "ReCaptchaV2TaskProxyLess", "websiteURL": page_url, "websiteKey": captcha_info["siteKey"]},
        "recaptcha-v3": {"type": "ReCaptchaV3TaskProxyLess", "websiteURL": page_url, "websiteKey": captcha_info["siteKey"], "pageAction": "submit"},
        "turnstile":    {"type": "AntiTurnstileTaskProxyLess", "websiteURL": page_url, "websiteKey": captcha_info["siteKey"]},
    }
    task_data = task_types.get(captcha_info["type"])
    if not task_data:
        raise Exception(f"Unsupported: {captcha_info['type']}")

    result = get_task_result(create_task(task_data))
    solution = result.get("solution", {})
    return solution.get("gRecaptchaResponse") or solution.get("token", "")

Java:

import java.net.http.*;
import java.net.URI;
import org.json.*;

String CAPSOLVER_API = "https://api.capsolver.com";
String API_KEY = System.getenv("CAPSOLVER_API_KEY");
HttpClient http = HttpClient.newHttpClient();

String createTask(JSONObject taskData) throws Exception {
    var body = new JSONObject()
        .put("clientKey", API_KEY)
        .put("task", taskData);
    var req = HttpRequest.newBuilder(URI.create(CAPSOLVER_API + "/createTask"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
        .build();
    var data = new JSONObject(http.send(req, HttpResponse.BodyHandlers.ofString()).body());
    if (data.getInt("errorId") != 0) throw new Exception("CapSolver: " + data.getString("errorDescription"));
    return data.getString("taskId");
}

JSONObject getTaskResult(String taskId) throws Exception {
    for (int i = 0; i < 60; i++) {
        Thread.sleep(2000);
        var body = new JSONObject().put("clientKey", API_KEY).put("taskId", taskId);
        var req = HttpRequest.newBuilder(URI.create(CAPSOLVER_API + "/getTaskResult"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
            .build();
        var data = new JSONObject(http.send(req, HttpResponse.BodyHandlers.ofString()).body());
        if ("ready".equals(data.getString("status"))) return data;
        if ("failed".equals(data.getString("status"))) throw new Exception("Failed: " + data.getString("errorDescription"));
    }
    throw new Exception("Timeout");
}

第 5 步:通过 Vibium 的 browser_evaluate 注入已解决的令牌

一旦你有了令牌,使用 JavaScript 评估将其注入页面的隐藏表单字段中。

注入 JavaScript 在所有语言中都相同——reCAPTCHA 回调遍历确保令牌被接受:

JavaScript:

function injectToken(page, captchaType, token) {
  if (captchaType === 'recaptcha-v2' || captchaType === 'recaptcha-v3') {
    page.evaluate(`
document.querySelector('textarea[name="g-recaptcha-response"]').value = "${token}";
      try {
        const clients = ___grecaptcha_cfg.clients;
        for (const id in clients) {
          const find = (o) => { for (const k in o) {
            if (typeof o[k]==='object' && o[k]!==null) {
              if (typeof o[k].callback==='function') { o[k].callback("${token}"); return true; }
              if (find(o[k])) return true;
            }
          } return false; };
          find(clients[id]);
        }
      } catch(e) {}
    `)
  } else if (captchaType === 'turnstile') {
    page.evaluate(`
      document.querySelector('input[name="cf-turnstile-response"]').value = "${token}";
      try { const cb = document.querySelector('.cf-turnstile')?.getAttribute('data-callback');
        if (cb && typeof window[cb]==='function') window[cb]("${token}");
      } catch(e) {}
    `)
  }
}

Python:

def inject_token(page, captcha_type: str, token: str):
    if captcha_type in ("recaptcha-v2", "recaptcha-v3"):
        page.evaluate(f"""
            document.querySelector('textarea[name="g-recaptcha-response"]').value = "{token}";
            try {{
                const clients = ___grecaptcha_cfg.clients;
                for (const id in clients) {{
                    const find = (o) => {{ for (const k in o) {{
                        if (typeof o[k]==='object' && o[k]!==null) {{
                            if (typeof o[k].callback==='function') {{ o[k].callback("{token}"); return true; }}
                            if (find(o[k])) return true;
                        }}
                    }} return false; }};
                    find(clients[id]);
                }}
            }} catch(e) {{}}
        """)
    elif captcha_type == "turnstile":
        page.evaluate(f"""
            document.querySelector('input[name="cf-turnstile-response"]').value = "{token}";
            try {{ const cb = document.querySelector('.cf-turnstile')?.getAttribute('data-callback');
                if (cb && typeof window[cb]==='function') window[cb]("{token}");
            }} catch(e) {{}}
        """)

Java:

void injectToken(Page page, String captchaType, String token) {
    if (captchaType.equals("recaptcha-v2") || captchaType.equals("recaptcha-v3")) {
        page.evaluate(String.format("""
            document.querySelector('textarea[name="g-recaptcha-response"]').value = "%s";
            try {
                const clients = ___grecaptcha_cfg.clients;
                for (const id in clients) {
                    const find = (o) => { for (const k in o) {
                        if (typeof o[k]==='object' && o[k]!==null) {
                            if (typeof o[k].callback==='function') { o[k].callback("%s"); return true; }
                            if (find(o[k])) return true;
                        }
                    } return false; };
                    find(clients[id]);
                }
            } catch(e) {}
            """, token, token));
    } else if (captchaType.equals("turnstile")) {
        page.evaluate(String.format("""
            document.querySelector('input[name="cf-turnstile-response"]').value = "%s";
            try { const cb = document.querySelector('.cf-turnstile')?.getAttribute('data-callback');
                if (cb && typeof window[cb]==='function') window[cb]("%s");
            } catch(e) {}
            """, token, token));
    }
}

第6步:提交表单

注入令牌后,使用Vibium的语义元素查找或标准点击提交表单。

// JavaScript
page.find('Submit').click()           // 语义查找
page.evaluate(`document.querySelector('#recaptcha-demo-submit')?.click()`)  // CSS
# Python
page.find("Submit").click()           # 语义查找
page.evaluate('document.querySelector("#recaptcha-demo-submit")?.click()')  # CSS
// Java
page.find("Submit").click();           // 语义查找
page.evaluate("document.querySelector('#recaptcha-demo-submit')?.click()"); // CSS

提交后,将结果页面文本或URL视为成功信号。不需要监控reCAPTCHA的内部/userverify请求。

第7步:完整工作示例

以下是一个完整的示例,导航到reCAPTCHA v2演示页面,通过CapSolver API解决CAPTCHA,注入令牌并提交表单。

测试说明:使用专用的测试密钥进行自动化测试。使用目标页面作为烟雾测试或参考流程,而不是作为长期自动化测试套件的基础。

JavaScript:

const { browser } = require('vibium/sync')

// (包括createTask, getTaskResult, solveCaptcha从步骤4)

const bro = browser.start()
const page = bro.page()

// 1. 导航
const targetUrl = 'https://example.com/protected-page'
page.go(targetUrl)

// 2. 检测
const info = page.evaluate(`(() => {
  const el = document.querySelector('.g-recaptcha');
  return el ? { type: 'recaptcha-v2', siteKey: el.getAttribute('data-sitekey') }
             : { type: 'none', siteKey: null };
})()`)

if (info.type === 'none') { console.log('未检测到CAPTCHA'); process.exit() }
console.log(`检测到${info.type} — 密钥${info.siteKey}`)

// 3. 解决
const token = await solveCaptcha(info, targetUrl)
console.log('已解决!')

// 4. 注入 + 提交
page.evaluate(`
  document.querySelector('textarea[name="g-recaptcha-response"]').value = "${token}";
  try { const c = ___grecaptcha_cfg.clients; for (const id in c) {
    const f = (o) => { for (const k in o) { if (typeof o[k]==='object'&&o[k]!==null) {
      if (typeof o[k].callback==='function'){o[k].callback("${token}");return true}
      if(f(o[k]))return true}} return false}; f(c[id]) }} catch(e){}
`)
page.evaluate(`document.querySelector('#recaptcha-demo-form').submit()`)

// 5. 验证
setTimeout(() => {
  console.log('结果:', page.evaluate('document.body.innerText'))
  bro.stop()
}, 2000)

Python:

from vibium import browser
import os, time, requests

CAPSOLVER_API = "https://api.capsolver.com"
API_KEY = os.environ["CAPSOLVER_API_KEY"]

# (包括create_task, get_task_result, solve_captcha从步骤4)

def main():
    bro = browser.start()
    page = bro.page()

    # 1. 导航
    target_url = "https://example.com/protected-page"
    page.go(target_url)

    # 2. 检测
    info = page.evaluate("""(() => {
        const el = document.querySelector('.g-recaptcha');
        return el ? { type: 'recaptcha-v2', siteKey: el.getAttribute('data-sitekey') }
                   : { type: 'none', siteKey: null };
    })()""")

    if info["type"] == "none":
        print("未检测到CAPTCHA。")
        return

    print(f"检测到{info['type']} — 密钥{info['siteKey']}")

    # 3. 解决
    token = solve_captcha(info, target_url)
    print("已解决!")

    # 4. 注入 + 提交
    page.evaluate(f"""
        document.querySelector('textarea[name="g-recaptcha-response"]').value = "{token}";
        try {{ const c = ___grecaptcha_cfg.clients; for (const id in c) {{
            const f = (o) => {{ for (const k in o) {{ if (typeof o[k]==='object'&&o[k]!==null) {{
                if (typeof o[k].callback==='function'){{o[k].callback("{token}");return true}}
                if(f(o[k]))return true}}}} return false}}; f(c[id]) }}}} catch(e){{}}
    """)
    page.evaluate('document.querySelector("#recaptcha-demo-form").submit()')

    # 5. 验证
    time.sleep(2)
    print("结果:", page.evaluate("document.body.innerText"))
    bro.stop()

main()

Java:

import com.vibium.Vibium;

// (包括createTask, getTaskResult从步骤4)

var bro = Vibium.start();
var page = bro.page();

// 1. 导航
var targetUrl = "https://example.com/protected-page";
page.go(targetUrl);

// 2. 检测
var info = (Map) page.evaluate("""
    (() => {
        const el = document.querySelector('.g-recaptcha');
        return el ? { type: 'recaptcha-v2', siteKey: el.getAttribute('data-sitekey') }
                   : { type: 'none', siteKey: null };
    })()""");

if ("none".equals(info.get("type"))) { System.out.println("未检测到CAPTCHA"); return; }
System.out.printf("检测到%s — 密钥%s%n", info.get("type"), info.get("siteKey"));

// 3. 解决
var taskData = new JSONObject()
    .put("type", "ReCaptchaV2TaskProxyLess")
    .put("websiteURL", targetUrl)
    .put("websiteKey", info.get("siteKey"));
var taskId = createTask(taskData);
var result = getTaskResult(taskId);
var token = result.getJSONObject("solution").getString("gRecaptchaResponse");
System.out.println("已解决!");

// 4. 注入 + 提交
page.evaluate(String.format("""
    document.querySelector('textarea[name="g-recaptcha-response"]').value = "%s";
    try { const c = ___grecaptcha_cfg.clients; for (const id in c) {
        const f = (o) => { for (const k in o) { if (typeof o[k]==='object'&&o[k]!==null) {
            if (typeof o[k].callback==='function'){o[k].callback("%s");return true}
            if(f(o[k]))return true}} return false}; f(c[id]) }} catch(e){}
    """, token, token));
page.evaluate("document.querySelector('#recaptcha-demo-form').submit()");

// 5. 验证
Thread.sleep(2000);
System.out.println("结果: " + page.evaluate("document.body.innerText"));
bro.stop();


支持的CAPTCHA任务类型

CAPTCHA CapSolver 任务类型 令牌字段 典型解决时间
reCAPTCHA v2 ReCaptchaV2TaskProxyLess textarea[name="g-recaptcha-response"] 5-15 秒
reCAPTCHA v2 (不可见) ReCaptchaV2TaskProxyLess textarea[name="g-recaptcha-response"] 5-15 秒
reCAPTCHA v3 ReCaptchaV3TaskProxyLess input[name="g-recaptcha-response"] 3-10 秒
reCAPTCHA 企业版 ReCaptchaV2EnterpriseTaskProxyLess textarea[name="g-recaptcha-response"] 10-20 秒
Cloudflare Turnstile AntiTurnstileTaskProxyLess input[name="cf-turnstile-response"] 3-10 秒
AWS WAF AntiAwsWafTaskProxyLess 自定义(因网站而异) 5-15 秒
GeeTest v3/v4 GeeTestTaskProxyLess 自定义(因网站而异) 5-15 秒

故障排除

令牌在表单提交前过期

症状:表单提交但服务器拒绝CAPTCHA响应。

原因:CAPTCHA令牌有有限的生命周期(通常reCAPTCHA为90-120秒,Turnstile为300秒)。如果脚本在解决和提交之间花费太长时间,令牌将过期。

解决方法:在收到令牌后立即注入并提交。不要在步骤5和步骤7之间添加不必要的延迟。

页面未检测到CAPTCHA

症状:检测脚本返回{ type: 'none' },尽管你可以看到CAPTCHA。

可能原因

  1. 页面未完全加载 — 导航后添加等待:time.sleep(3)
  2. CAPTCHA加载在iframe中 — 一些reCAPTCHA实现加载在iframe中。你可能需要检测iframe并从页面源或网络请求中提取站点密钥
  3. 动态加载 — CAPTCHA小部件可能异步加载。在检测前等待元素出现

CapSolver API返回错误

常见错误

错误 原因 解决方法
ERROR_KEY_DOES_NOT_EXIST 无效的API密钥 检查你的CAPSOLVER_API_KEY
ERROR_ZERO_BALANCE 余额不足 capsolver.com充值
ERROR_WRONG_CAPTCHA_TYPE 为该CAPTCHA使用了错误的任务类型 通过检测助手验证CAPTCHA类型
ERROR_CAPTCHA_UNSOLVABLE CAPTCHA无法解决 重试 — 有时会失败

调用CapSolver API时出现CORS问题

症状:从浏览器调用API时出现CORS错误。

原因:你正在从browser_evaluate(即浏览器上下文中)调用CapSolver API。CapSolver API不允许来自任意网站的跨域请求。

解决方法:始终从你的脚本(Node.js、Python或Java进程)调用CapSolver API,而不是从浏览器内部调用。仅在浏览器中使用browser_evaluate进行检测(读取DOM)和注入(设置表单值)。API调用必须在服务器端进行。

表单提交未触发

症状:令牌已注入,但表单未提交或服务器未接受它。

可能原因

  1. 缺少回调触发 — 很多reCAPTCHA实现需要将回调函数与令牌一起调用,而不仅仅是设置文本区域的值。参见上面的injectToken函数,它遍历___grecaptcha_cfg.clients以找到并调用回调
  2. 自定义表单验证 — 网站可能有额外的JavaScript验证。在开发者工具中检查表单的提交处理程序
  3. 令牌格式不匹配 — 确保从CapSolver结果中使用gRecaptchaResponse用于reCAPTCHA和token用于Turnstile

最佳实践

1. 以合理间隔轮询

2秒轮询/getTaskResult。更频繁的轮询会浪费API调用并可能触发速率限制。更少的轮询会增加不必要的延迟。

// JavaScript: 良好 — 2秒间隔
await new Promise(r => setTimeout(r, 2000))
# Python: 良好 — 2秒间隔
time.sleep(2)
// Java: 良好 — 2秒间隔
Thread.sleep(2000);

2. 实现带指数退避的重试逻辑

CAPTCHA解决偶尔会失败。将你的解决函数包装在重试中:

JavaScript:

async function solveWithRetry(info, url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try { return await solveCaptcha(info, url) }
    catch (e) {
      if (i === retries - 1) throw e
      await new Promise(r => setTimeout(r, 2 ** i * 1000))
    }
  }
}

Python:

def solve_with_retry(info, url, retries=3):
    for i in range(retries):
        try: return solve_captcha(info, url)
        except Exception:
            if i == retries - 1: raise
            time.sleep(2 ** i)

3. 为每个CAPTCHA使用正确的任务类型

使用错误的任务类型会导致解决失败。首先检测CAPTCHA类型,然后映射到正确的CapSolver任务:

CAPTCHA 任务类型
reCAPTCHA v2 (复选框) ReCaptchaV2TaskProxyLess
reCAPTCHA v2 (不可见) ReCaptchaV2TaskProxyLess
reCAPTCHA v3 ReCaptchaV3TaskProxyLess
reCAPTCHA v2 企业版 ReCaptchaV2EnterpriseTaskProxyLess
reCAPTCHA v3 企业版 ReCaptchaV3EnterpriseTaskProxyLess
Cloudflare Turnstile AntiTurnstileTaskProxyLess
AWS WAF AntiAwsWafTaskProxyLess

4. 立即注入并提交

CAPTCHA令牌会过期。一旦从CapSolver收到令牌,立即注入并提交表单。不要在解决和提交之间添加人工延迟。

5. 在长跑前检查余额

JavaScript:

const res = await fetch(`${CAPSOLVER_API}/getBalance`, {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ clientKey: API_KEY }),
})
const { balance } = await res.json()
if (balance < 1) console.warn('低 CapSolver 余额!')

Python:

balance = requests.post(f"{CAPSOLVER_API}/getBalance",
    json={"clientKey": API_KEY}).json().get("balance", 0)
if balance < 1:
    print("低 CapSolver 余额!")

6. 将 API 调用保持在服务器端

永远不要在 browser_evaluate 中调用 CapSolver API。浏览器上下文的 HTTP 请求会因 CORS 而失败,并且在浏览器 JavaScript 中暴露你的 API 密钥存在安全风险。始终从你的应用程序进程(Node.js、Python 或 Java)中进行 API 调用。


结论

Vibium + CapSolver API 集成表明,你不需要浏览器扩展即可在自动化流程中解决验证码。当工具如 Vibium 限制 Chrome 标志时,API 方法为你提供了更多控制,而非更少

  1. 通过 browser_evaluate 检测验证码类型和站点密钥
  2. 通过从你的脚本调用 CapSolver REST API 解决问题
  3. 通过 browser_evaluate 将令牌注入页面
  4. 提交表单

此模式适用于任何支持 JavaScript 评估的浏览器自动化工具——不仅仅是 Vibium。无论你使用 WebDriver BiDi、CDP 或任何其他协议,CapSolver API 方法都是通用的。

通过将 Vibium 的基于标准的浏览器自动化与 CapSolver 快速可靠的验证码解决 API 相结合,你可以获得一个强大的流程,可以处理 reCAPTCHA、Turnstile、AWS WAF 等——而无需使用任何浏览器扩展。


准备好了吗? 注册 CapSolver 并使用优惠码 VIBIUM 在首次充值时获得额外 6% 的奖励!


FAQ

为什么我不能在 Vibium 中使用 CapSolver Chrome 扩展?

Vibium 的 Go 启动器在启动 Chrome 时硬编码了 --disable-extensions。没有配置选项或标志覆盖来更改此行为。像 Playwright 和 OpenClaw 这样的工具使用的扩展方法在 Vibium 中不起作用。API 方法是正确的集成方式。

什么是 API 方法?

而不是依赖浏览器扩展来自动检测和解决验证码,你可以直接从你的脚本调用 CapSolver REST API。通过 JavaScript 评估在页面上检测验证码,将站点密钥和 URL 发送到 CapSolver 的 createTask 端点,轮询 getTaskResult 直到解决方案就绪,然后将令牌注入页面。

CapSolver 支持哪些验证码类型?

CapSolver 支持 reCAPTCHA v2(复选框和不可见)、reCAPTCHA v3、reCAPTCHA 企业版、Cloudflare Turnstile、AWS WAF 验证码、GeeTest、DataDome 等。请参阅完整列表

CapSolver 的费用是多少?

CapSolver 提供基于验证码类型和数量的具有竞争力的定价。访问 capsolver.com 查看当前定价。使用优惠码 VIBIUM 在首次充值时获得额外 6% 的奖励。

Vibium 是免费的吗?

Vibium 是开源且免费的。你可以从 GitHub 下载 Go 二进制文件。对于验证码解决,你需要一个具有积分的 CapSolver 账户。

我可以将此方法与其他 BiDi 工具一起使用吗?

可以。API 方法适用于任何支持 JavaScript 评估的浏览器自动化工具,无论其使用 WebDriver BiDi、CDP 还是其他协议。唯一的要求是在页面上运行 JavaScript 以检测验证码并注入已解决的令牌。

解决验证码需要多长时间?

典型的解决时间取决于验证码类型:reCAPTCHA v2 需要 5-15 秒,reCAPTCHA v3 需要 3-10 秒,Cloudflare Turnstile 需要 3-10 秒。你的总墙时间包括轮询间隔(每次轮询 2 秒)。

验证码令牌会过期吗?

是的。reCAPTCHA 令牌通常在 90-120 秒后过期。Turnstile 令牌最多持续 300 秒。一旦收到令牌,应立即注入并提交。

提交表单后是否需要监控 /userverify

不需要。将 /userverify 视为 reCAPTCHA 的内部网络流量,而不是主要的成功信号。提交后,检查可见的结果页面、确认文本或目标 URL。

我可以在自动化测试中使用公共演示页面吗?

仅用作烟雾测试。为自动化测试流程创建专用的测试密钥,而不是依赖任何公共演示页面。

如何找到验证码站点密钥?

站点密钥嵌入在页面 HTML 中。对于 reCAPTCHA,查找 .g-recaptcha 元素上的 data-sitekey 属性。对于 Turnstile,检查 .cf-turnstile 上的 data-sitekey 属性。本指南中的检测辅助函数会自动提取它。

我可以将此方法与 Vibium 的 MCP 服务器一起使用吗?

可以。当通过 Vibium 的 MCP 服务器使用 Vibium 时,代理可以调用 CapSolver API 作为工具,然后使用 browser_evaluate 注入令牌。无论由脚本还是 AI 代理驱动,流程都是相同的——检测、解决、注入、提交。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐