Use Cases

Playwright 验证码处理实战:搭配 CaptchaAI 自动识别

Playwright 本身不会识别验证码——脚本跑到 reCAPTCHA 或 Cloudflare Turnstile 页面时只能干等。真正完成识别的是 CaptchaAI:服务器端解出验证码,Playwright 只负责提取 sitekey、把 token 写回页面,两者配合就能让自动化流程稳定跑完验证码环节。

Playwright 验证码处理为什么离不开 CaptchaAI

Playwright 的自动等待解决"元素何时能点",不解决"验证码怎么过"。

国内站点常见 GeeTest(极验),reCAPTCHA、Turnstile 多见于海外站点;reCAPTCHA 需加载 Google 域名脚本,境内网络下常偏慢,不少团队因此直接用 CaptchaAI API 识别,减少对浏览器内交互式验证的依赖。

环境准备

  • Pythonpip install playwright requests,然后执行 playwright install
  • Node.jsnpm install playwright axios
  • CaptchaAI API Key:在 captchaai.com 注册后获取

Playwright、Selenium、Puppeteer 怎么选

三者接入 CaptchaAI 的方式一样——提取 sitekey、调用 API 识别、写回 token,选框架前先看清楚差异:

对比项 Playwright Selenium Puppeteer
支持语言 Python、Node.js、C#、Java Python、Java、C#、Ruby、JavaScript Node.js
支持浏览器 Chromium、Firefox、WebKit Chrome、Firefox、Edge、Safari Chromium
自动等待 ✅ 内置 ❌ 需手动等待 ⚠️ 部分支持
网络拦截 ✅ 支持 ⚠️ 有限 ✅ 支持
CaptchaAI 集成 ✅ 接口相同 ✅ 接口相同 ✅ 接口相同

下面以 Playwright 为例,看具体怎么接入。

Python 实战:Playwright + CaptchaAI 识别验证码

三步走:

  1. 封装识别函数——提交任务并轮询结果
  2. 完整登录示例——把 token 写回页面并提交表单
  3. 异步版本——并发处理多个页面

封装识别函数

向 CaptchaAI 提交任务,每隔几秒轮询一次结果:

from playwright.sync_api import sync_playwright
import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_recaptcha(site_key, page_url):
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url
    })
    if not resp.text.startswith("OK|"):
        raise Exception(resp.text)
    task_id = resp.text.split("|")[1]

    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 == "CAPCHA_NOT_READY": continue
        if result.text.startswith("OK|"): return result.text.split("|")[1]
        raise Exception(result.text)
    raise TimeoutError()

完整登录示例

拿到 token 后写回页面元素,再提交表单:

def login_with_captcha(url, username, password):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        )
        page = context.new_page()
        page.goto(url)

        # Fill login form
        page.fill("#username", username)
        page.fill("#password", password)

        # Check for reCAPTCHA
        recaptcha = page.query_selector(".g-recaptcha")
        if recaptcha:
            site_key = recaptcha.get_attribute("data-sitekey")
            print(f"Solving reCAPTCHA: {site_key}")

            token = solve_recaptcha(site_key, page.url)

            # Inject token
            page.evaluate(f"""
                document.getElementById('g-recaptcha-response').innerHTML = '{token}';
                document.getElementById('g-recaptcha-response').style.display = '';
            """)

        # Submit
        page.click('button[type="submit"]')
        page.wait_for_load_state("networkidle")

        print(f"Current URL: {page.url}")
        content = page.content()

        browser.close()
        return content

result = login_with_captcha(
    "https://staging.example.com/qa-login",
    "[email protected]",
    "password123"
)

异步版本

并发处理多个页面时,用 aiohttp 替掉 requests

from playwright.async_api import async_playwright
import aiohttp
import asyncio

async def solve_recaptcha_async(site_key, page_url):
    async with aiohttp.ClientSession() as session:
        params = {
            "key": API_KEY, "method": "userrecaptcha",
            "googlekey": site_key, "pageurl": page_url
        }
        async with session.get("https://ocr.captchaai.com/in.php", params=params) as resp:
            text = await resp.text()
            task_id = text.split("|")[1]

        for _ in range(60):
            await asyncio.sleep(5)
            params = {"key": API_KEY, "action": "get", "id": task_id}
            async with session.get("https://ocr.captchaai.com/res.php", params=params) as resp:
                text = await resp.text()
                if text == "CAPCHA_NOT_READY": continue
                if text.startswith("OK|"): return text.split("|")[1]
                raise Exception(text)
        raise TimeoutError()

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        await page.goto("https://example.com/form")

        site_key = await page.get_attribute(".g-recaptcha", "data-sitekey")
        token = await solve_recaptcha_async(site_key, page.url)

        await page.evaluate(f"document.getElementById('g-recaptcha-response').innerHTML = '{token}'")
        await page.click('button[type="submit"]')
        await browser.close()

asyncio.run(main())

Node.js 实战:Playwright + CaptchaAI

思路一样,换成 axios 发请求:

const { chromium } = require("playwright");
const axios = require("axios");

const API_KEY = "YOUR_API_KEY";

async function solveRecaptcha(siteKey, pageUrl) {
  const submit = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: API_KEY,
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: pageUrl,
    },
  });
  const taskId = submit.data.split("|")[1];

  while (true) {
    await new Promise((r) => setTimeout(r, 5000));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });
    if (result.data === "CAPCHA_NOT_READY") continue;
    if (result.data.startsWith("OK|")) return result.data.split("|")[1];
    throw new Error(result.data);
  }
}

(async () => {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto("https://staging.example.com/qa-login");

  // Fill form
  await page.fill("#username", "[email protected]");
  await page.fill("#password", "password123");

  // Solve CAPTCHA
  const siteKey = await page.getAttribute(".g-recaptcha", "data-sitekey");
  if (siteKey) {
    const token = await solveRecaptcha(siteKey, page.url());
    await page.evaluate(
      (t) => (document.getElementById("g-recaptcha-response").innerHTML = t),
      token
    );
  }

  // Submit
  await page.click('button[type="submit"]');
  await page.waitForLoadState("networkidle");

  console.log("Logged in:", page.url());
  await browser.close();
})();

识别 Cloudflare Turnstile

检测和提交方式跟 reCAPTCHA 几乎一致,区别只在 method 参数换成 turnstile

# Detect Turnstile
turnstile = page.query_selector(".cf-turnstile")
if turnstile:
    site_key = turnstile.get_attribute("data-sitekey")

    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY, "method": "turnstile",
        "sitekey": site_key, "pageurl": page.url
    })
    task_id = resp.text.split("|")[1]

    # Poll and inject...

排查清单

  • page.query_selector 返回 null:验证码异步加载,用 page.wait_for_selector() 等它出现后再取值
  • token 提交没生效:检查 response textarea 的实际 ID 是否一致
  • Playwright 在 Docker 里跑不起来:缺浏览器依赖,执行 playwright install-deps 补齐
  • 识别通过后验证码又弹出来:站点可能要求触发回调,用 page.evaluate() 手动执行一次

常见问题

Playwright 的自动等待能顺带把验证码解决掉吗?

不能,自动等待只保证元素出现后再操作,遇到验证码依旧要靠 CaptchaAI 的 API 才能解出。

CaptchaAI 支持 Playwright 会遇到的所有验证码类型吗?

覆盖大部分常见类型:reCAPTCHA v2/v3、Cloudflare Turnstile、GeeTest v3、图片/九宫格验证码,CaptchaFox、Friendly Captcha、Lemin(均为测试版)也支持。hCaptcha、FunCaptcha 暂不支持。

CaptchaAI 是按识别次数收费的吗?

不是,按并发线程计费。BASIC 计划 $15/月含 5 个线程,线程内识别次数不限;量大就换更高线程数的套餐,如 ADVANCE $90/月、50 线程。

Headless 模式下验证码出现的概率会不会更高?

会更高一些,这是网站反爬虫策略的正常现象,不代表脚本写错了。出现的验证码直接交给 CaptchaAI 识别即可。

相关指南

该文章已禁用评论。