Use Cases

使用验证码处理自动提交表单

用 Selenium 跑自动化表单提交时,验证码是绕不开的一道关卡:联系表单、申请表、注册页只要挂了 reCAPTCHA、Turnstile 或图片验证码,脚本填完字段也提交不出去。思路并不复杂——先识别页面上是哪种验证码,调用 CaptchaAI 拿到 token,再写回表单完成提交,下面是一套可以直接复用的实现。


什么场景会用到这套流程

跑 QA 回归、批量提交测试申请表、验证联系表单能否正常收信,只要目标页面带验证码,脚本一到提交就卡住。国内站点常见极验(GeeTest)滑块,CaptchaAI 对接的是 GeeTest v3;测试海外 SaaS 表单则更常撞上 reCAPTCHA v2 或 Turnstile,这也是下面示例优先覆盖它们的原因。思路都一样:探测 → 识别 → 回填 → 提交。


整体架构

四步走完一次提交:加载页面、填字段、探测并识别验证码、点击提交。

┌────────────┐     ┌──────────────┐     ┌────────────┐     ┌──────────────┐
│ Load Form  │────▶│ Fill Fields  │────▶│ Detect &   │────▶│ Submit Form  │
│ (Selenium) │     │              │     │ Solve      │     │              │
│            │     │              │     │ CAPTCHA    │     │              │
└────────────┘     └──────────────┘     └────────────┘     └──────────────┘

三个类各管一段:FormCaptchaSolver 对话 CaptchaAI API,CaptchaDetector 判断验证码类型,FormAutomator 负责填字段和提交。


三个核心组件

验证码识别器

封装 in.php 提交任务、res.php 轮询结果,首次查询前先等 10 秒,太快查会白白占用线程。

import time
import requests


class FormCaptchaSolver:
    BASE = "https://ocr.captchaai.com"

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

    def solve(self, params, initial_wait=10):
        params["key"] = self.api_key
        params["json"] = 1
        resp = requests.post(f"{self.BASE}/in.php", data=params).json()
        if resp["status"] != 1:
            raise Exception(f"Submit error: {resp['request']}")

        task_id = resp["request"]
        time.sleep(initial_wait)

        for _ in range(60):
            result = requests.get(
                f"{self.BASE}/res.php",
                params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
            ).json()
            if result["request"] == "CAPCHA_NOT_READY":
                time.sleep(5)
                continue
            if result["status"] == 1:
                return result["request"]
            raise Exception(f"Solve error: {result['request']}")
        raise TimeoutError("CAPTCHA solve timed out")

验证码探测器

填表单前先扫一遍页面:有没有 .cf-turnstile[data-sitekey]、验证码图片元素,判断类型和 sitekey 或图片地址。

import re
from selenium.webdriver.common.by import By


class CaptchaDetector:
    def __init__(self, driver):
        self.driver = driver

    def detect(self):
        """Detect CAPTCHA type on current page."""
        html = self.driver.page_source

        # Turnstile
        turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile, [data-sitekey]")
        for el in turnstile:
            if "cf-turnstile" in (el.get_attribute("class") or ""):
                return "turnstile", el.get_attribute("data-sitekey")

        # reCAPTCHA
        recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
        if recaptcha:
            sitekey = recaptcha[0].get_attribute("data-sitekey")
            if "recaptcha" in html.lower():
                return "recaptcha_v2", sitekey

        # Image CAPTCHA
        img = self.driver.find_elements(By.CSS_SELECTOR, "img[src*='captcha'], img.captcha")
        if img:
            return "image", img[0].get_attribute("src")

        return "none", None

表单自动化控制器

命中 reCAPTCHA 走 userrecaptcha,命中 Turnstile 走 turnstile,图片验证码下载转 base64 后走 base64 方法,拿到 token 用 execute_script 写入隐藏字段。

import base64
import requests as req
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


class FormAutomator:
    def __init__(self, api_key):
        self.solver = FormCaptchaSolver(api_key)
        self.driver = webdriver.Chrome()
        self.detector = CaptchaDetector(self.driver)

    def fill_field(self, selector, value):
        field = WebDriverWait(self.driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, selector))
        )
        field.clear()
        field.send_keys(value)

    def select_option(self, selector, value):
        from selenium.webdriver.support.ui import Select
        select = Select(self.driver.find_element(By.CSS_SELECTOR, selector))
        select.select_by_value(value)

    def solve_captcha(self):
        captcha_type, data = self.detector.detect()
        page_url = self.driver.current_url

        if captcha_type == "recaptcha_v2":
            token = self.solver.solve({
                "method": "userrecaptcha",
                "googlekey": data,
                "pageurl": page_url,
            })
            self.driver.execute_script(
                f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
            )
            return True

        if captcha_type == "turnstile":
            token = self.solver.solve({
                "method": "turnstile",
                "sitekey": data,
                "pageurl": page_url,
            })
            self.driver.execute_script(
                f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
            )
            return True

        if captcha_type == "image":
            img_data = req.get(data).content
            img_b64 = base64.b64encode(img_data).decode()
            text = self.solver.solve({"method": "base64", "body": img_b64})
            captcha_input = self.driver.find_element(
                By.CSS_SELECTOR, "input[name*='captcha']"
            )
            captcha_input.clear()
            captcha_input.send_keys(text)
            return True

        return False  # No CAPTCHA detected

    def submit_form(self, url, fields, submit_selector="button[type='submit']"):
        """
        fields: list of (selector, value) tuples
        """
        self.driver.get(url)

        for selector, value in fields:
            self.fill_field(selector, value)

        self.solve_captcha()

        submit = self.driver.find_element(By.CSS_SELECTOR, submit_selector)
        submit.click()

        return self.driver.current_url

    def close(self):
        self.driver.quit()

完整示例:联系表单

三个类拼起来之后,调用方只需给出目标页面和字段值,识别验证码这一步在 submit_form() 内部自动处理:

automator = FormAutomator("YOUR_API_KEY")

try:
    result_url = automator.submit_form(
        url="https://example.com/contact",
        fields=[
            ("#name", "John Doe"),
            ("#email", "[email protected]"),
            ("#subject", "Sales inquiry"),
            ("#message", "I'd like to learn more about your services."),
        ],
        submit_selector="#submit-btn",
    )
    print(f"Form submitted. Redirected to: {result_url}")
finally:
    automator.close()

处理多种表单类型

同一套 FormAutomator,换个 urlfields 就能覆盖登录、注册、搜索这几类表单。

登录表单

result = automator.submit_form(
    url="https://staging.example.com/qa-login",
    fields=[
        ("#username", "testuser"),
        ("#password", "testpass123"),
    ],
    submit_selector="#login-btn",
)

注册表单

字段更多,流程完全一样:

result = automator.submit_form(
    url="https://example.com/register",
    fields=[
        ("#first-name", "Jane"),
        ("#last-name", "Smith"),
        ("#email", "[email protected]"),
        ("#password", "SecurePass!123"),
        ("#confirm-password", "SecurePass!123"),
    ],
    submit_selector="#register-btn",
)

带验证码的搜索表单

有些站点只在触发风控时才弹验证码,探测不到就直接跳过:

result = automator.submit_form(
    url="https://example.com/search",
    fields=[
        ("#query", "python developer"),
        ("#location", "San Francisco"),
    ],
    submit_selector="#search-btn",
)

两个容易漏掉的情况

  • 验证失败后才出现的验证码:每次提交后都重新调用 solve_captcha(),别只在最开始探测一次。
  • AJAX 表单execute_script 写隐藏字段未必生效,改成拦截请求、把 token 塞进负载里更可靠。

故障排查

问题 原因 处理方式
令牌被拒绝 token 在提交前已过期 最后一步再识别验证码,识别完立即提交
找不到表单字段 页面动态加载,元素还没渲染出来 WebDriverWait 加显式等待,不要用固定 sleep
探测到错误的验证码类型 页面上同时存在多个验证码相关元素 检查 detect() 里的判断顺序,精确匹配放前面
提交后表单又刷新回来 服务器端校验失败,不一定是验证码问题 先查必填字段和格式,再确认 token 是否正确回填
reCAPTCHA 写入了却没生效 站点用了回调而不是读取隐藏字段 写入 token 后手动触发 grecaptcha.execute()

目标页面不是自己站点时,先确认已获授权,只在自有或已授权的 staging/QA 环境里跑这套脚本。


常见问题

不开浏览器,能直接提交带验证码的表单吗?

reCAPTCHA 和 Turnstile 可以:拿到 token 后直接 HTTP POST 提交即可。但表单依赖前端 JavaScript 校验或动态渲染时,还是得走 Selenium。

表单一直被拒绝,是不是 token 的问题?

先看时效性:token 通常一两分钟内失效,把“识别—提交”间隔压到最短,中间别插入额外请求或等待。

GeeTest 的表单能接进这套流程吗?

可以,在 CaptchaDetector 里加一段 GeeTest 元素判断,method 换成 geetest 即可。CaptchaAI 目前支持 GeeTest v3,v4 官方标注为即将支持,暂时不能当作已支持来用。

批量跑几十个表单会被限流吗?

限流看套餐线程数,不是表单数量。BASIC($15/月,5 线程)线程内可无限次识别,超出并发就升级套餐或排队处理。

CaptchaAI 能识别 hCaptcha 吗?

暂不支持,FunCaptcha(Arkose Labs)同样不支持。目前覆盖 reCAPTCHA v2/v3、Turnstile 与 Cloudflare Challenge、GeeTest v3、图片/OCR、九宫格及 BLS。


相关指南


把表单提交这一步彻底自动化——用 CaptchaAI 识别验证码

该文章已禁用评论。