Tutorials

Python Selenium + CaptchaAI 完整集成指南

Selenium 脚本跑到登录页那一步,突然弹出 reCAPTCHA、Turnstile 或一张图片验证码——流程就卡住了。原因很简单:Selenium 只负责渲染页面和执行 JavaScript,它本身不会“看懂”验证码。打通这一步的做法是:让 Selenium 提取页面上的 sitekey,交给 CaptchaAI 识别,拿到 token 后用 JavaScript 写回页面,再照常提交。

本指南按实际接入顺序给出 reCAPTCHA v2/v3、Cloudflare Turnstile 和图片验证码的完整代码,每一段都能直接复制进项目。


环境准备

开始之前先准备好这三样:

  • Python 3.8 及以上版本
  • CaptchaAI 控制台里生成的 API Key
  • 本地已安装 Chrome(webdriver-manager 自动匹配 ChromeDriver)
pip install selenium requests webdriver-manager

国内网络访问 PyPI 官方源经常很慢,建议加镜像参数:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple selenium requests webdriver-manager


第一步:创建标准配置的 Selenium driver

create_driver() 封装标准启动配置,并移除 navigator.webdriver 标志,避免被简单地判定为自动化浏览器:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

def create_driver():
    """Create a Selenium driver with stealth-configuredion settings."""
    options = webdriver.ChromeOptions()
    options.add_argument("--no-sandbox")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option("useAutomationExtension", False)

    driver = webdriver.Chrome(
        service=Service(ChromeDriverManager().install()),
        options=options,
    )

    # Remove webdriver flag
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
    })

    return driver

封装一个通用的 CaptchaAI 求解函数

把提交任务、轮询结果封装成一个函数,后面每种验证码类型直接调用即可:

import time
import requests

API_KEY = "YOUR_API_KEY"

def solve_captcha(method, **params):
    """Generic CaptchaAI solver — works for all supported CAPTCHA types."""
    submit_data = {
        "key": API_KEY,
        "method": method,
        "json": 1,
        **params,
    }

    submit = requests.post("https://ocr.captchaai.com/in.php", data=submit_data, timeout=30)
    data = submit.json()

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

    task_id = data["request"]

    for _ in range(30):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=30).json()

        if result.get("status") == 1:
            return result["request"]
        if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
            raise Exception("CAPTCHA unsolvable")

    raise TimeoutError("Solve timed out")

用 Selenium 识别 reCAPTCHA v2

完整流程:提取 sitekey、拿到 token、注入并提交

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

def solve_recaptcha_v2_selenium(driver, url):
    """Complete reCAPTCHA v2 solve in Selenium."""
    driver.get(url)

    # Wait for page to load
    WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.TAG_NAME, "body"))
    )

    # Extract sitekey
    page_source = driver.page_source
    match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]{40})["\']', page_source)
    if not match:
        raise ValueError("reCAPTCHA sitekey not found")
    sitekey = match.group(1)
    print(f"Sitekey: {sitekey}")

    # Solve via CaptchaAI
    token = solve_captcha(
        "userrecaptcha",
        googlekey=sitekey,
        pageurl=url,
    )
    print(f"Token received: {token[:50]}...")

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

    # If there's a callback function, call 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.rr && client.rr.l) {{
                    client.rr.l.callback('{token}');
                }}
            }}
        }}
    """)

    # Submit the form
    submit_button = driver.find_element(By.CSS_SELECTOR, "button[type='submit'], input[type='submit']")
    submit_button.click()

    return token

# Usage
driver = create_driver()
try:
    solve_recaptcha_v2_selenium(driver, "https://staging.example.com/qa-login")
    print(f"Current URL: {driver.current_url}")
finally:
    driver.quit()

用 Selenium 识别 Cloudflare Turnstile

Turnstile 的 sitekey 有时不在 data-sitekey 属性里,而是写在 JavaScript 配置对象中,所以下面多加了一层正则兜底:

提取 sitekey、求解、注入 token

def solve_turnstile_selenium(driver, url):
    """Complete Turnstile solve in Selenium."""
    driver.get(url)

    WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.TAG_NAME, "body"))
    )

    # Extract sitekey
    page_source = driver.page_source
    match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', page_source)
    if not match:
        # Try JavaScript API pattern
        match = re.search(r"sitekey\s*:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", page_source)
    if not match:
        raise ValueError("Turnstile sitekey not found")

    sitekey = match.group(1)
    print(f"Turnstile sitekey: {sitekey}")

    # Solve via CaptchaAI
    token = solve_captcha(
        "turnstile",
        sitekey=sitekey,
        pageurl=url,
    )
    print(f"Turnstile token: {token[:50]}...")

    # Inject token
    driver.execute_script(f"""
        // Set the hidden input value
        var inputs = document.querySelectorAll('[name="cf-turnstile-response"]');
        inputs.forEach(function(input) {{ input.value = '{token}'; }});

        // Also try the callback approach
        if (typeof turnstile !== 'undefined' && turnstile.getResponse) {{
            // Widget already rendered
        }}
    """)

    # Submit form
    submit_button = driver.find_element(By.CSS_SELECTOR, "button[type='submit'], input[type='submit']")
    submit_button.click()

    return token

图片验证码:截图 + CaptchaAI 识别

传统图片/文字验证码不用提取 sitekey,直接截图发给 CaptchaAI 识别即可:

截图并识别

import base64

def solve_image_captcha_selenium(driver, captcha_selector):
    """Solve image CAPTCHA visible in the browser."""
    # Find the CAPTCHA image element
    captcha_img = driver.find_element(By.CSS_SELECTOR, captcha_selector)

    # Get image as base64
    img_base64 = captcha_img.screenshot_as_base64

    # Solve via CaptchaAI
    answer = solve_captcha("base64", body=img_base64)
    print(f"CAPTCHA answer: {answer}")

    # Type the answer into the input field
    captcha_input = driver.find_element(
        By.CSS_SELECTOR, "input[name='captcha'], input[name='code'], input.captcha-input"
    )
    captcha_input.clear()
    captcha_input.send_keys(answer)

    return answer

用 Selenium 识别 reCAPTCHA v3

reCAPTCHA v3 没有挑战框,只返回分数,不需要检测环节,直接提交 sitekey 和 action:

提交 sitekey 和 action,直接拿分数

def solve_recaptcha_v3_selenium(driver, url, sitekey, action="verify"):
    """Solve reCAPTCHA v3 in Selenium."""
    driver.get(url)

    # Solve via CaptchaAI
    token = solve_captcha(
        "userrecaptcha",
        googlekey=sitekey,
        pageurl=url,
        version="v3",
        action=action,
    )

    # Inject token
    driver.execute_script(f"""
        // Set reCAPTCHA response
        var textarea = document.getElementById('g-recaptcha-response');
        if (textarea) {{
            textarea.value = '{token}';
            textarea.style.display = 'block';
        }}

        // Set any hidden input fields
        var inputs = document.querySelectorAll('input[name="g-recaptcha-response"]');
        inputs.forEach(function(input) {{ input.value = '{token}'; }});
    """)

    return token

完整示例:一个自动检测类型的 SeleniumCaptchaSolver

整合成一个类,detect_captcha() 先判断验证码类型,再调用对应方法:

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 re
import time
import requests

API_KEY = "YOUR_API_KEY"

class SeleniumCaptchaSolver:
    """Complete Selenium + CaptchaAI automation class."""

    def __init__(self, api_key):
        self.api_key = api_key
        self.driver = None

    def start(self):
        """Initialize the browser."""
        options = webdriver.ChromeOptions()
        options.add_argument("--no-sandbox")
        options.add_experimental_option("excludeSwitches", ["enable-automation"])
        self.driver = webdriver.Chrome(options=options)
        self.driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
            "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
        })

    def stop(self):
        """Close the browser."""
        if self.driver:
            self.driver.quit()

    def navigate(self, url):
        """Navigate to URL and wait for load."""
        self.driver.get(url)
        WebDriverWait(self.driver, 15).until(
            lambda d: d.execute_script("return document.readyState") == "complete"
        )

    def detect_captcha(self):
        """Detect which CAPTCHA type is on the page."""
        source = self.driver.page_source

        if re.search(r'data-sitekey=["\'][A-Za-z0-9_-]{40}["\']', source):
            if "recaptcha/api.js" in source or "grecaptcha" in source:
                return "recaptcha_v2"

        if "cf-turnstile" in source or "challenges.cloudflare.com/turnstile" in source:
            return "turnstile"

        if re.search(r'recaptcha.*v3|render=[A-Za-z0-9_-]{40}', source):
            return "recaptcha_v3"

        captcha_imgs = self.driver.find_elements(
            By.CSS_SELECTOR, "img.captcha, img[alt*='captcha'], img[src*='captcha']"
        )
        if captcha_imgs:
            return "image"

        return None

    def solve_and_submit(self, url, form_data=None):
        """Navigate, solve CAPTCHA, fill form, and submit."""
        self.navigate(url)
        captcha_type = self.detect_captcha()

        if not captcha_type:
            print("No CAPTCHA detected")
            return self._fill_and_submit(form_data)

        print(f"Detected: {captcha_type}")

        if captcha_type == "recaptcha_v2":
            self._solve_recaptcha_v2()
        elif captcha_type == "turnstile":
            self._solve_turnstile()
        elif captcha_type == "image":
            self._solve_image()

        return self._fill_and_submit(form_data)

    def _solve_recaptcha_v2(self):
        source = self.driver.page_source
        match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]{40})["\']', source)
        sitekey = match.group(1)

        token = self._api_solve("userrecaptcha", googlekey=sitekey, pageurl=self.driver.current_url)

        self.driver.execute_script(f"""
            document.getElementById('g-recaptcha-response').value = '{token}';
        """)

    def _solve_turnstile(self):
        source = self.driver.page_source
        match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', source)
        sitekey = match.group(1)

        token = self._api_solve("turnstile", sitekey=sitekey, pageurl=self.driver.current_url)

        self.driver.execute_script(f"""
            document.querySelectorAll('[name="cf-turnstile-response"]')
                .forEach(function(el) {{ el.value = '{token}'; }});
        """)

    def _solve_image(self):
        img = self.driver.find_element(
            By.CSS_SELECTOR, "img.captcha, img[alt*='captcha'], img[src*='captcha']"
        )
        answer = self._api_solve("base64", body=img.screenshot_as_base64)

        captcha_input = self.driver.find_element(
            By.CSS_SELECTOR, "input[name='captcha'], input[name='code']"
        )
        captcha_input.clear()
        captcha_input.send_keys(answer)

    def _fill_and_submit(self, form_data):
        if form_data:
            for name, value in form_data.items():
                try:
                    field = self.driver.find_element(By.NAME, name)
                    field.clear()
                    field.send_keys(value)
                except Exception:
                    pass

        submit = self.driver.find_element(
            By.CSS_SELECTOR, "button[type='submit'], input[type='submit']"
        )
        submit.click()
        time.sleep(3)
        return self.driver.current_url

    def _api_solve(self, method, **params):
        submit = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key, "method": method, "json": 1, **params,
        }, timeout=30)
        data = submit.json()
        if data.get("status") != 1:
            raise Exception(f"Submit error: {data.get('request')}")

        task_id = data["request"]
        for _ in range(30):
            time.sleep(5)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key, "action": "get", "id": task_id, "json": 1,
            }, timeout=30).json()
            if result.get("status") == 1:
                return result["request"]
        raise TimeoutError("Solve timed out")

# Usage
solver = SeleniumCaptchaSolver(API_KEY)
solver.start()
try:
    result_url = solver.solve_and_submit(
        "https://staging.example.com/qa-login",
        form_data={"username": "[email protected]", "password": "pass123"},
    )
    print(f"Result: {result_url}")
finally:
    solver.stop()

国内电商、社区类站点常见的是 GeeTest(极验),可以按同样思路给 detect_captcha() 加一个分支:判断页面是否出现 gtchallenge 参数,再调用 geetest 方法求解。


故障排除

先确认 token 注入的元素是否正确、页面是否在 iframe 里、token 是否拿到后隔太久才提交。仍无头绪时,对照下表逐行排除:

症状 原因 处理方式
检测到验证码但 token 提交失败 页面中找不到 g-recaptcha-response 检查是否在 iframe 中——reCAPTCHA 经常渲染在 iframe 里
token 已注入但表单未提交 回调函数未触发 显式调用 reCAPTCHA 的回调函数
网站提示检测到 WebDriver 标准配置未生效 添加 CDP 命令移除 navigator.webdriver 标志
图片验证码截图空白 元素还不可见 先把元素滚动到可视区域再截图
Turnstile token 被拒绝 sitekey 提取错误 从当前页面源重新提取一次 sitekey

常见问题

如何判断页面用的是 reCAPTCHA v2 还是 v3?

v2 有可点击的挑战框,data-sitekey 通常是 40 位字符;v3 没有挑战框,sitekey 一般写在页面脚本的 render= 参数里,返回分数而非“验证通过”提示。detect_captcha() 就是按这个区别判断的。

Selenium 可以用无头(headless)模式运行吗?

可以,但部分网站会检测无头模式并额外拦截。建议用 --headless=new 而不是旧版 --headless,保留正常的 WebGL/Canvas 特征。CaptchaAI 在自己的节点上识别,跟 Selenium 是否无头无关。

国内网络访问 reCAPTCHA 慢或加载不出来,会影响识别吗?

reCAPTCHA 脚本由 Google 域名提供,国内网络访问有时不稳定甚至超时——这是页面加载问题,与 CaptchaAI 无关。只要控件渲染出来、sitekey 能提取,后续 token 请求走的是 CaptchaAI 自己的接口。

webdriver-manager 一直下载不了 ChromeDriver 怎么办?

多数是网络问题:给 pip install 加镜像参数,或手动下载对应版本 ChromeDriver,把路径传给 Service(executable_path="...")

什么时候该用 Selenium,什么时候直接用 requests 就够?

纯 API 表单提交,拿到 token 填进请求体即可,用 requests 更快更省资源;页面依赖 JavaScript 渲染或多步跳转时才需要 Selenium。两者 CaptchaAI 识别耗时一样,差别只在浏览器启动开销。


总结

Python Selenium + CaptchaAI 能处理浏览器自动化里的各类验证码:检测类型、调用 API 识别、注入 token、提交表单。SeleniumCaptchaSolver 类把这套流程封装成了可直接复用的工作流。

相关文章

该文章已禁用评论。