Integrations

undetected-chromedriver + CaptchaAI:Python 验证码识别集成

undetected-chromedriver(简称 UC)能让 Selenium 少触发一部分验证码,却不负责“做题”。页面真弹出 reCAPTCHA v2 或 Turnstile 时,仍需接口取回 token 写进表单。本文补齐这条链路:UC 驱动 Chrome,CaptchaAI 返回 token。仅限自有或经授权的 QA、staging 环境。


环境准备

依赖项 说明
CaptchaAI API Key CaptchaAI 官网 注册获取
Python 3.8+ 建议用虚拟环境
Chrome UC 自动匹配驱动
pip install undetected-chromedriver requests

国内拉包慢可加 -i https://pypi.tuna.tsinghua.edu.cn/simple 走清华源。


一、启动被修补过的 Chrome

UC 会替换若干默认自动化特征,并自动下载匹配本机 Chrome 的驱动。

import undetected_chromedriver as uc
import requests
import time


def create_stealth_browser():
    """Create an undetected Chrome browser instance."""
    options = uc.ChromeOptions()
    options.add_argument("--no-sandbox")
    options.add_argument("--window-size=1920,1080")

    driver = uc.Chrome(options=options)
    return driver

二、reCAPTCHA v2 的三步流程

第 1 步:取出 sitekey

sitekey 一般挂在带 data-sitekey 的容器上;若用 iframe 渲染,从 srck= 后那段。

API_KEY = "YOUR_API_KEY"


def extract_recaptcha_sitekey(driver):
    """Extract reCAPTCHA v2 sitekey from the page."""
    try:
        element = driver.find_element("css selector", "[data-sitekey]")
        return element.get_attribute("data-sitekey")
    except Exception:
        # Try finding in iframe src
        iframes = driver.find_elements("css selector", "iframe[src*='recaptcha']")
        for iframe in iframes:
            src = iframe.get_attribute("src")
            if "k=" in src:
                return src.split("k=")[1].split("&")[0]
    return None

第 2 步:提交任务并轮询

in.php 提交 userrecaptcha 任务,拿到 ID 后轮询 res.php:首次等 20 秒,之后每 5 秒一次;CAPCHA_NOT_READY 表示仍在识别。

def solve_recaptcha_v2(sitekey, pageurl):
    """Submit reCAPTCHA v2 to CaptchaAI and return the token."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1
    }).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit error: {submit.get('request')}")

    task_id = submit["request"]

    time.sleep(20)
    for _ in range(30):
        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:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)

    raise TimeoutError("Solve timed out")

第 3 步:把 token 写回页面

token 填进 g-recaptcha-response 隐藏域;若页面注册回调,还需手动触发。

def inject_recaptcha_token(driver, token):
    """Inject the solved token into the page and submit."""
    driver.execute_script(f'''
        document.getElementById("g-recaptcha-response").innerHTML = "{token}";
        document.getElementById("g-recaptcha-response").style.display = "block";
    ''')

    # If there's a callback function, trigger it
    driver.execute_script(f'''
        if (typeof ___grecaptcha_cfg !== 'undefined') {{
            var clients = ___grecaptcha_cfg.clients;
            for (var key in clients) {{
                var client = clients[key];
                if (client && client.callback) {{
                    client.callback("{token}");
                }}
            }}
        }}
    ''')

三、完整示例:带 reCAPTCHA 的登录表单

示例地址为自建 staging 环境,请换成你有权限测试的页。

import undetected_chromedriver as uc
import requests
import time

API_KEY = "YOUR_API_KEY"


def solve_recaptcha(sitekey, pageurl):
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY, "method": "userrecaptcha",
        "googlekey": sitekey, "pageurl": pageurl, "json": 1
    }).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit error: {submit.get('request')}")

    task_id = submit["request"]
    time.sleep(20)

    for _ in range(30):
        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:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)
    raise TimeoutError("Solve timed out")


def main():
    driver = uc.Chrome()

    try:
        # Navigate to target page
        driver.get("https://staging.example.com/qa-login")
        time.sleep(3)

        # Fill in form fields
        driver.find_element("id", "username").send_keys("user")
        driver.find_element("id", "password").send_keys("pass")

        # Extract sitekey
        element = driver.find_element("css selector", "[data-sitekey]")
        sitekey = element.get_attribute("data-sitekey")
        pageurl = driver.current_url
        print(f"Sitekey: {sitekey}")

        # Solve CAPTCHA
        token = solve_recaptcha(sitekey, pageurl)
        print(f"Token: {token[:50]}...")

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

        # Submit form
        driver.find_element("id", "submit-btn").click()
        time.sleep(3)

        print(f"Current URL: {driver.current_url}")
    finally:
        driver.quit()


if __name__ == "__main__":
    main()

常见坑:站点做多语言路由时中英文页 sitekey 不同,取错会一直失败。


四、Cloudflare Turnstile 的差异

三处不同:method 改为 turnstilegooglekey 换成 sitekey、回填字段是 cf-turnstile-response;耗时更短,首次等待压到 10 秒。

def solve_turnstile(sitekey, pageurl):
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY, "method": "turnstile",
        "sitekey": sitekey, "pageurl": pageurl, "json": 1
    }).json()

    if submit.get("status") != 1:
        raise RuntimeError(f"Submit error: {submit.get('request')}")

    task_id = submit["request"]
    time.sleep(10)

    for _ in range(30):
        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:
            return result["request"]
        if result.get("request") != "CAPCHA_NOT_READY":
            raise RuntimeError(f"Solve error: {result['request']}")
        time.sleep(5)
    raise TimeoutError("Solve timed out")


# Inject Turnstile token
def inject_turnstile_token(driver, token):
    driver.execute_script(f'''
        var input = document.querySelector('[name="cf-turnstile-response"]');
        if (input) input.value = "{token}";
    ''')

能力边界:支持 reCAPTCHA v2/v3、Turnstile、GeeTest v3、图片与九宫格验证码;CaptchaFox、Friendly Captcha、Lemin 为测试版,hCaptcha 与 FunCaptcha 暂不支持。国内多用 GeeTest(极验),须确认为 v3。


五、故障排查表

问题 原因 处理方式
版本不匹配 UC 找不到驱动 升级 Chrome 或指定 version_main
验证码仍出现 站点特征采集更细 属预期,交 CaptchaAI
表单不通过 元素 ID 或回调不对 核对页面 reCAPTCHA 实现
WebDriverException Chrome 崩溃 --no-sandbox
一直 CAPCHA_NOT_READY 线程不足 拉长上限或升级

六、并发与套餐怎么估

线程计费而非按次,套餐内次数不限,只需估算同时有多少实例在等 token:单机调试 BASIC($15/月,5 线程),5~15 并发 STANDARD($30/月,15 线程),CI 并行 ADVANCE($90/月,50 线程)。


常见问题

用了 UC 之后还需要识别服务吗?

需要。UC 减少的是自动化特征暴露;只要站点判定要人机校验,组件仍会出现,得靠接口返回 token。

无头模式和 Selenium Grid 下怎么跑?

无头加 options.add_argument("--headless=new") 即可,但触发验证码更频繁;Grid 须在每个节点预修补驱动。

轮询一直超时怎么排查?

status 不为 1 说明任务没进队列,多半是 API Key 或余额问题;久不返回则是并发超线程数。

识别成功率大概多少?

内部观测样本中 reCAPTCHA v2 与 Turnstile 表现稳定,但数值受站点与网络影响,属参考值,请自行实测;采集须遵循 robots 协议。


下一步

CaptchaAI 官网 注册拿 API Key,把 solve_recaptcha() 接进 UC 脚本即可。


延伸阅读

该文章已禁用评论。