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() 加一个分支:判断页面是否出现 gt、challenge 参数,再调用 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 类把这套流程封装成了可直接复用的工作流。