核心要点
- Selenium 中出现 CAPTCHA 通常是被识别为机器人活动的表现。
- 应先做好预防,而非等出现后再解决。
- 代理应作为完整浏览器身份指纹的一部分使用,而非单独依赖。
在运行 Selenium Python 脚本时,出现 CAPTCHA 拦截并不是偶然现象,它通常说明网站已察觉到某些信号,使您的 Selenium 会话看起来不像典型的真人浏览行为。
因此,在网站的机器人检测机制打断您的工作流程之前,理解 CAPTCHA 绕过技巧十分重要。本文将介绍绕过 Selenium CAPTCHA 问题的常见方法,包括 Selenium Stealth、纯净代理的使用,以及 CAPTCHA 解决方案工作流。
为什么 Selenium 会触发 CAPTCHA
首先,来看看您正在抓取或测试的网站为何会弹出那个令人烦恼的“证明你是人类”验证框。当浏览器配置、IP 地址或浏览行为开始显得像自动化操作时,Selenium 就可能触发 CAPTCHA 拦截。
以下是一些可能的原因:
- 浏览器指纹与 WebDriver 信号
浏览器指纹是浏览器在与网站交互时暴露的一系列细节信息。当会话声称自己是 Windows 上的 Chrome,但其他信号却显示出虚拟化、自动化或设备配置不一致时,出现 CAPTCHA 的概率就会上升。
- 无头浏览器检测
Selenium 可以以无头模式或有头模式运行 Chrome。无头模式的优势在于浏览器在后台运行,但它也可能留下一些网站能够识别的信号,例如异常的窗口尺寸、虚拟图形渲染,或缺乏正常用户活动的迹象。
- 不自然的行为模式
真实用户不会以完全均匀的节奏浏览网页。他们会停顿、不规律地滚动页面、阅读内容、点击前犹豫,并做出细微的动作。而一个基础的 Selenium 脚本往往是打开页面、等待一秒、点击、瞬间输入、提交,并在多个页面上重复这套动作,这会让整个会话显得像自动化操作。
- 信誉较差或使用过度的 IP
即使浏览器配置看起来很干净,一个信誉较差的 IP 地址也可能在网站检查您的行为之前就触发 CAPTCHA。免费或使用过度的代理可能早已与垃圾请求或反复出现的 CAPTCHA 验证挂钩,导致会话一开始就带有较高的风险评分。
- 缺乏会话连续性
正常浏览器会在 Cookie、缓存、本地存储以及之前访问过的页面路径中保留历史记录。而基础的 Selenium 配置每次访问都以全新的浏览器配置文件开始,这会让每次访问都像是一次全新的临时会话。当这种“干净”的会话又表现出快速操作或直接跳转到重要页面的特征时,触发 CAPTCHA 的概率就会更高。
更有效的CAPTCHA 绕过方法是分层进行:先让 Selenium 更难被检测,再提升代理质量和行为模式,只有在仍然出现验证时才使用解决方案工具。
方法一:使用更隐身的 Selenium 浏览器配置
了解了 Selenium 触发 CAPTCHA 的原因后,下一步就是从源头减少让浏览器看起来像自动化操作的信号。
undetected-chromedriver
标准 Selenium 会暴露以下信号:
- navigator.webdriver === true
- 缺失或不完整的 Chrome 运行时对象
- DevTools Protocol 中的自动化标记
与标准 Selenium 不同,可以安装 undetected-chromedriver。它会在浏览器启动前修补部分 Chrome 组件,使会话不携带一些最明显的 Selenium 指纹特征。
undetected-chromedriver 可以做到:
- 移除 ChromeDriver 中已知的指纹标记字符串“cdc_”
- 重写 navigator.webdriver 的行为,使其返回 undefined
- 补充缺失的 Chrome 对象,例如 window.chrome 及其运行时属性
- 关闭“Chrome 正受到自动化测试软件控制”的提示条
最终效果是浏览器看起来像一个正常的 Chrome 实例,而不是被 Selenium WebDriver 控制的浏览器。
去除明显的自动化信号
像 Selenium Stealth 这样的工具可以帮助去除明显的自动化信号。Selenium Stealth 是一个 Python 包,能够降低被 Selenium 控制的 Chrome/Chromium 被识别为机器人的概率,其原理是修改浏览器指纹属性,防止暴露自动化痕迹。
最常见的信号包括:
- navigator.webdriver === true
- Chrome 或 Edge 中缺失window.chrome对象
- 不完整的插件与语言列表
- navigator.connection 缺失或异常
- Chrome DevTools Protocol 标记
- ChromeDriver 中的 cdc_ 字符串
- User-Agent 与平台信息不匹配
方法二:通过代理提升 IP 信誉
配置好更隐身的 Selenium 浏览器后,下一步是搭配信誉良好的代理供应商所提供的代理。IP 质量至关重要,因为网站可能会记录曾用于数据抓取、垃圾请求、登录失败或反复出现 CAPTCHA 验证的地址。
如果 Selenium 会话使用的 IP 地址已被标记,即使清理了浏览器设置,仍可能反复遇到 CAPTCHA。
住宅代理、数据中心代理与移动代理的对比
为 Selenium 选择代理时,通常会遇到三种选项:住宅代理、数据中心代理和移动代理。
住宅代理在避免 CAPTCHA 方面尤其有效,因为它们通过真实用户设备转发请求,这让目标网站认为该流量更加合法。
在以下情况下使用住宅代理:
- 正在抓取机器人检测较严格的网站
- 需要更接近正常用户浏览行为的流量
- 正在处理对地理位置敏感的页面
- 希望降低反复触发 Google reCAPTCHA 的风险
- 需要在信任度与可扩展性之间取得更好的平衡
数据中心代理虽然速度快、价格实惠,但相比住宅代理更容易被网站检测和封锁。在防护严密的网站上使用数据中心代理可能适得其反,导致 CAPTCHA 验证更频繁地出现。
在以下情况下使用数据中心代理:
- 正在抓取防护较弱的网站
- 需要更快的响应速度
- 以受控节奏采集公开页面
- 在使用更高信任度的代理之前先测试 Selenium 代码
- 不需要 IP 看起来像住宅流量
移动代理使用由移动运营商分配的 IP,因此看起来信誉度很高。适用于以下情况:
- 正在处理以移动端为主的网站
- 需要基于运营商的 IP 用于位置测试
- 住宅代理仍然过于频繁地被验证挑战
- 目标网站对数据中心流量较为敏感
轮换代理与黏性会话
您可以选择轮换代理,也可以在一段时间内使用同一个 IP,这一选择会直接影响您的配置是否会触发 CAPTCHA。使用代理轮换 IP 地址有助于降低数据抓取过程中触发 CAPTCHA 的概率,因为这样可以让爬虫看起来像多个自然用户。
除此之外,您的 IP 应作为更大身份配置的一部分。轮换 IP 时,也应同时轮换该配置中的其他所有信号,否则会话会变得不一致,更容易被识别。
一个完整的身份配置应包含以下内容:
- 时区
- User-Agent
- 视口大小
- 硬件并发数(navigator.hardwareConcurrency)
- 触控点数(navigator.maxTouchPoints)
- 语言及 Accept-Language 请求头
- WebGL 供应商字符串
- WebGL 渲染器字符串
- 设备内存(navigator.deviceMemory)
如果只轮换代理而保留所有其他指纹信号不变,几乎必然会触发 CAPTCHA。轮换更适合重复性任务,例如Selenium 网络爬取,将请求分散到多个 IP 上可以降低单一地址所承受的压力。而黏性会话则更适合登录平台或执行需要保持一致身份的操作,这类场景通常要求正常用户维持稳定的身份特征。
方法三:优化会话与行为管理
Selenium 脚本的运行方式会直接影响 CAPTCHA 出现的频率。在会话处理和页面导航方式上做的每一处细微调整,累积起来会产生明显效果。
以下是一些能让爬虫看起来更自然的具体技巧。
复用 Cookie 与会话
首先,尝试在脚本的多次运行之间复用 Cookie 和会话。带有一组熟悉 Cookie 出现的浏览器,看起来远不像一个全新的临时浏览器。可使用以下函数:
- save_cookies(driver, filename) 将所有 Cookie 写入一个 JSON 文件
- load_cookies(driver, filename) 在导航到目标域名之前,读取该文件并将 Cookie 添加回浏览器
Selenium 脚本通过 undetected-chromedriver 启动浏览器时,还应使用持久化的用户数据目录,这样本地存储、会话存储及其他浏览器状态都能在多次运行之间保持不变。
避免反复冷启动
冷启动是指在目标网站上没有任何历史活动的情况下,直接启动一个全新的浏览器会话,网站会察觉到这一点。
一个凭空出现、没有任何已存储 Cookie、缓存或历史记录的浏览器,看起来就像是一个刚搭建好的全新爬虫,这本身就可能触发 CAPTCHA 弹窗。更好的做法是让浏览器实例在多次请求之间保持开启和活跃状态。如果 Selenium WebDriver 会话持续运行,网站会认为这只是同一位真实访客再次访问。
需要重启浏览器时,先进行一次简单的预热浏览,不要直接跳转到目标抓取页面。花一两分钟浏览一些无关网站,例如新闻主页或 Wikipedia 文章。
让浏览器稍微滚动一下页面,以便页面上的分析脚本记录更自然的交互,然后再导航到实际要抓取的域名。这段预热时间有助于补全一些机器人检测系统所寻找的缺失信号。
使用更接近真实用户的等待与导航模式
写 time.sleep(2) 这种代码,会明显暴露自动化特征——你的 Selenium 脚本每次都会精确地暂停两秒。说实话,没有真人会这样做。
真人可能会瞄一眼手机,也可能因为早就知道按钮位置而直接点击。关键在于,真人的行为并不一致,你的脚本也应如此。
您可以这样做:
- 用随机等待时间替代固定等待:比起始终固定等待 2 秒,像 random.uniform(1.5, 8.0) 这样的等待方式更接近真实用户行为。
- 对于加载较慢的元素,可以使用 WebDriverWait 并设置灵活的超时时间,同时在轮询间隔中加入小幅随机抖动,不要每隔精确的 0.1 秒检查一次,应让间隔有所变化。
- 此外,Selenium 脚本不应每次都以相同顺序点击所有链接或按钮,而应随机打乱顺序,使行为更接近真人。如果是在搜索结果页面,可以让脚本先输入带有几个拼写错误的搜索词,然后再进行修正;有时点错内容、点击返回再重试,也是可以的。
降低请求量,避免重试高峰
请求频率是决定能否避开 CAPTCHA 的关键因素之一:长期保持较低且稳定的请求频率,比突发性的高频请求更安全。
如果需要抓取大量页面,应将任务分散到数小时甚至数天内完成,并在请求之间加入随机间隔。对于设有严格机器人检测系统的网站,将请求频率控制在每个 IP 每分钟 10 次以内是比较合理的起点。
重试高峰尤其危险:请求失败时,许多脚本会立即连续重试两三次,这种流量突增看起来就像是自动化程序在猛烈敲击服务器。
更好的做法是采用指数退避策略:第一次失败后等待 5 秒,第二次等待 15 秒,第三次等待 45 秒。如果代理或会话反复失败,应切换到新的 IP 及其匹配的指纹,而不是继续使用同一个。
方法四:使用 CAPTCHA 解决服务
到目前为止介绍的方法都是防御性的,目的是减少让 Selenium WebDriver 会话显得像自动化程序的信号,从而在 CAPTCHA 干扰工作流程之前就将其避免。
但如果脚本依然触发了 CAPTCHA,该怎么办?
这正是 CAPTCHA 解决服务发挥作用的地方:这种方法能让脚本在 Selenium 中遇到 CAPTCHA 验证时进行相应处理。
这一流程通常如下:
- 步骤一:脚本遇到 CAPTCHA
- 步骤二:将验证码信息发送给解决服务
- 步骤三:该服务通过人工或自动识别系统进行解答
- 步骤四:脚本接收解答结果
- 步骤五:脚本提交解答结果并继续会话
重要的是,在将这些工具加入 Selenium 配置之前,先了解它们的工作原理。
基于令牌的解决方式与交互式解决方式
在使用 Selenium Python 脚本进行网络爬取时,可能遇到两类 CAPTCHA 验证:基于令牌的验证和交互式验证。
- 基于令牌的解决方式常见于 reCAPTCHA、hCaptcha 等现代 CAPTCHA 系统。此时,目标网站更关心脚本是否持有有效令牌,再据此决定下一步操作。脚本会将 CAPTCHA 信息发送给 API,API 随后返回一个令牌,脚本可用其继续会话。
- 交互式点击验证则是另一回事。一些更复杂的 CAPTCHA 需要用户点击图片中的特定区域、拖动滑块或完成其他交互操作。脚本遇到这类验证时,会将验证信息发送给 API,API 随后返回完成任务所需的坐标或操作列表。
支持的验证类型
不同服务商所支持的验证类型有所差异,但大多数信誉良好的 CAPTCHA 解决服务,例如 CapSolver,都能覆盖用户在 Selenium 自动化过程中常遇到的 CAPTCHA 类型。
- Google reCAPTCHA v2包含常见的“我不是机器人”复选框。用户点击后,系统可能立即放行,也可能在无法确认用户身份时触发额外的图片方格验证,要求用户识别交通信号灯、公交车、人行横道或自行车等物体。
- Google reCAPTCHA v3是一种隐形的、基于评分的系统,在后台运行,无需用户进行直接交互。它不会显示复选框或图片验证,而是监测行为信号并给出评分,帮助网站判断访客更像真人还是自动化程序。若想深入了解这一评分机制的运作方式,请参阅我们关于如何绕过 reCAPTCHA v3的指南。
- hCaptcha是一种注重隐私保护的 reCAPTCHA 替代方案,通常以图片分类形式呈现验证,并使用自有的检测算法判断访客是否为真人。许多网站用 hCaptcha 取代 Google 的 reCAPTCHA 系统。
- 图片验证码是一种较传统的验证方式,要求用户正确输入图片中扭曲变形的字母、数字或单词。这类验证在现代网站上已不常见,但仍出现在部分表单、登录页面及较老旧的平台上。
- 语音验证码为视力障碍用户提供了另一种验证方式:用户无需完成视觉验证,而是聆听语音播报的字符、数字或单词,并输入所听到的内容。
- Cloudflare Turnstile是一种依赖令牌验证和后台检测、而非传统解谜方式的 CAPTCHA 替代方案,在许多情况下几乎无需用户进行可见操作即可完成验证。
使用 CAPTCHA 解决服务的利弊权衡
成本
CAPTCHA 解决服务单次解答的费用通常不高,但具体成本取决于验证类型:简单的图片验证码费用较低,而 reCAPTCHA v2、reCAPTCHA Enterprise、FunCaptcha 等类型的验证费用通常更高。
延迟
解答需要时间:有些验证只需几秒即可完成,有些则可能需要 20 到 30 秒甚至更久,这会拖慢 Selenium 的执行流程,尤其是在大规模运行时。
可靠性
解决服务并非万无一失:简单验证的请求成功率通常较高,而复杂的图片方格或交互式验证失败率更高,此外也可能出现超时或 API 报错的情况。
可扩展性
大多数解决服务的 API 都能处理并发请求,但脚本仍需具备重试逻辑,并在解答失败或耗时过长时进行降级处理
在 Selenium 中处理 CAPTCHA 的分步流程
在 Selenium 中绕开 CAPTCHA 需要多层次的方法,既包括测试阶段的架构调整,也包括在生产环境中集成专门的 AI 解决服务。
接下来,我们通过一个实际流程,展示如何从一开始就编写能够避免 CAPTCHA 的 Selenium 脚本。
搭建隐身浏览器环境
以下是一个基础脚本,使用 undetected_chromedriver 并注入部分 CDP 脚本来搭建隐身浏览器。
import undetected_chromedriver as uc
def launch_stealth_browser():
options = uc.ChromeOptions()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("--window-size=1366,768")
options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/121.0.0.0 Safari/537.36")
driver = uc.Chrome(options=options)
# Inject stealth scripts before any page loads
stealth_script = """
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
window.chrome = { runtime: {} };
Object.defineProperty(navigator, 'plugins', { get: () => [1,2,3,4,5] });
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
"""
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": stealth_script})
# Override timezone and geolocation
driver.execute_cdp_cmd("Emulation.setTimezoneOverride", {"timezoneId": "Europe/London"})
driver.execute_cdp_cmd("Emulation.setGeolocationOverride", {"latitude": 51.5074, "longitude": -0.1278, "accuracy": 100})
return driver
# Usage
driver = launch_stealth_browser()
driver.get("https://bot.sannysoft.com")
input("Check the page for automation flags, then press Enter to quit.")
driver.quit()
加入行为调优与会话持久化
仅有隐身浏览器还不够,脚本还需表现得像真人操作,包括复用 Cookie、以自然延迟输入文字,以及加入随机停顿。
以下这个基础脚本展示了这些要素如何配合使用。
import json
import random
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
def save_cookies(driver, filename):
with open(filename, "w") as f:
json.dump(driver.get_cookies(), f)
def load_cookies(driver, filename):
driver.get("https://example.com") # Navigate to domain first
with open(filename, "r") as f:
cookies = json.load(f)
for cookie in cookies:
driver.add_cookie(cookie)
def human_type(element, text):
element.click()
# Clear field with random backspaces
for _ in range(random.randint(2, 8)):
element.send_keys(Keys.BACKSPACE)
time.sleep(random.uniform(0.03, 0.1))
for char in text:
# 7% chance of hesitation
if random.random() < 0.07:
time.sleep(random.uniform(0.2, 0.6))
element.send_keys(char)
time.sleep(random.uniform(0.08, 0.3))
def random_wait(min_sec=1, max_sec=5):
time.sleep(random.uniform(min_sec, max_sec))
# Example usage
driver = launch_stealth_browser() # from previous example
driver.get("https://google.com")
random_wait(2, 4)
search_box = driver.find_element(By.NAME, "q")
human_type(search_box, "hello world")
random_wait(1, 2)
search_box.send_keys(Keys.RETURN)
# Save cookies after session
save_cookies(driver, "my_cookies.json")
driver.quit()
# Next run: load cookies
driver = launch_stealth_browser()
load_cookies(driver, "my_cookies.json")
driver.refresh()
如果 CAPTCHA 仍然出现,加入代理
不要只是把代理接入脚本就期望效果变好,每个 IP 地址都需要匹配的完整浏览器身份指纹,包括独特的 User-Agent、视口大小、时区、WebGL 供应商信息和语言请求头。以下代码片段展示了具体做法:
import undetected_chromedriver as uc
import random
import time
from selenium.webdriver.common.by import By
# Proxy pool (residential proxies with auth)
PROXIES = [
"http://user1:pass1@proxy1.example.com:8080",
"http://user2:pass2@proxy2.example.com:8080",
]
# Simple identity profiles (just user agent and viewport for demo)
IDENTITIES = [
{"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/121", "viewport": (1366,768)},
{"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605", "viewport": (1440,900)},
]
def assign_proxy_and_identity():
proxy = random.choice(PROXIES)
identity = random.choice(IDENTITIES)
return proxy, identity
def launch_browser_with_proxy(proxy, identity):
options = uc.ChromeOptions()
options.add_argument(f"--proxy-server={proxy}")
options.add_argument(f"--window-size={identity['viewport'][0]},{identity['viewport'][1]}")
options.add_argument(f"--user-agent={identity['user_agent']}")
options.add_argument("--disable-blink-features=AutomationControlled")
return uc.Chrome(options=options)
def captcha_detected(driver):
# Basic detection – look for keywords in page text
body = driver.find_element(By.TAG_NAME, "body").text.lower()
indicators = ["captcha", "verify you are human", "recaptcha"]
return any(ind in body for ind in indicators)
# Main retry loop
MAX_RETRIES = 3
for attempt in range(MAX_RETRIES):
proxy, identity = assign_proxy_and_identity()
print(f"Attempt {attempt+1}: Proxy={proxy.split('@')[1]}, Identity={identity['user_agent'][:30]}...")
driver = launch_browser_with_proxy(proxy, identity)
driver.get("https://target-site-that-may-show-captcha.com")
time.sleep(3)
if captcha_detected(driver):
print("CAPTCHA detected. Rotating proxy and identity...")
driver.quit()
wait_time = (attempt + 1) * 10 # exponential backoff
print(f"Waiting {wait_time} seconds before retry.")
time.sleep(wait_time)
continue # next attempt with new proxy/identity
# No CAPTCHA – proceed with scraping
print("No CAPTCHA. Scraping successful.")
# ... do your work ...
driver.quit()
break
else:
print("All attempts failed due to CAPTCHA.")
仅在必要时加入解决服务
如果已经使用 undetected-chromedriver、调整了行为模式并加入了代理,但目标网站仍要求脚本解答 CAPTCHA,可以使用下面这段脚本作为备用方案:
import time
import requests
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# === CONFIGURATION ===
CAPTCHA_SOLVER_API_KEY = "YOUR_API_KEY_HERE"
CAPTCHA_SOLVER_URL = "https://api.your-solver-service.com/in.php" # Example endpoint
CAPTCHA_SOLVER_RESULT_URL = "https://api.your-solver-service.com/res.php"
# === CAPTCHA DETECTION ===
def is_captcha_present(driver):
"""Return True if a CAPTCHA iframe or text is detected."""
try:
# Check for common CAPTCHA elements
if driver.find_elements(By.CSS_SELECTOR, "iframe[src*='recaptcha'], .g-recaptcha"):
return True
body_text = driver.find_element(By.TAG_NAME, "body").text.lower()
indicators = ["captcha", "verify you are human", "recaptcha"]
return any(ind in body_text for ind in indicators)
except:
return False
# === SOLVER INTEGRATION ===
def solve_captcha(driver):
"""
Send the CAPTCHA challenge to a solver service and return the solution token.
This example assumes a token‑based solver (reCAPTCHA v2/v3).
"""
# Step 1: Extract the site key (for reCAPTCHA) – adjust selector as needed
site_key = None
try:
# Look for the data-sitekey attribute on a div or iframe
captcha_element = driver.find_element(By.CSS_SELECTOR, ".g-recaptcha")
site_key = captcha_element.get_attribute("data-sitekey")
except:
# Fallback: some pages use a different structure
try:
iframe = driver.find_element(By.CSS_SELECTOR, "iframe[src*='recaptcha']")
# You may need to parse the src URL to get site key, but simplified here
pass
except:
pass
if not site_key:
raise Exception("Could not find site key for CAPTCHA")
# Step 2: Send the challenge to the solver service
payload = {
"key": CAPTCHA_SOLVER_API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": driver.current_url,
"json": 1
}
response = requests.post(CAPTCHA_SOLVER_URL, data=payload)
result = response.json()
if result.get("status") != 1:
raise Exception(f"Solver submission failed: {result}")
request_id = result.get("request")
print(f"CAPTCHA submitted, request ID: {request_id}")
# Step 3: Poll for the solution (wait up to 60 seconds)
for _ in range(30): # 30 * 2 seconds = 60 seconds max
time.sleep(2)
poll_payload = {
"key": CAPTCHA_SOLVER_API_KEY,
"action": "get",
"id": request_id,
"json": 1
}
poll_resp = requests.get(CAPTCHA_SOLVER_RESULT_URL, params=poll_payload)
poll_data = poll_resp.json()
if poll_data.get("status") == 1:
token = poll_data.get("request")
print(f"CAPTCHA solved, token received: {token[:20]}...")
return token
elif poll_data.get("request") == "CAPCHA_NOT_READY":
continue
else:
raise Exception(f"Solver error: {poll_data}")
raise Exception("Timeout waiting for CAPTCHA solution")
# === INJECT THE SOLUTION ===
def inject_captcha_token(driver, token):
"""Inject the solution token into the page and submit."""
# For reCAPTCHA, the token is usually injected into a hidden textarea
try:
driver.execute_script(f"document.getElementById('g-recaptcha-response').innerHTML = '{token}';")
except:
pass
# Then submit the form (or call the callback)
try:
submit_button = driver.find_element(By.CSS_SELECTOR, "button[type='submit'], input[type='submit']")
submit_button.click()
except:
# Some pages need to trigger the callback manually
driver.execute_script("onSuccessCaptcha(arguments[0]);", token)
time.sleep(3) # wait for page to process
# === MAIN WORKFLOW WITH FALLBACK ===
def scrape_with_captcha_fallback(driver):
"""
Perform your scraping. If a CAPTCHA appears, solve it and continue.
"""
# ... your navigation and interaction code ...
# After each major action, check for CAPTCHA
if is_captcha_present(driver):
print("CAPTCHA detected. Attempting to solve...")
try:
solution = solve_captcha(driver)
inject_captcha_token(driver, solution)
# Wait for the page to reload and re-check
time.sleep(5)
if is_captcha_present(driver):
print("CAPTCHA still present after solving. Might need a retry.")
return False
else:
print("CAPTCHA solved successfully. Continuing.")
except Exception as e:
print(f"CAPTCHA solving failed: {e}")
return False
# Proceed with scraping
print("No CAPTCHA blocking – extracting data...")
# ... your data extraction ...
return True
# === EXAMPLE USAGE ===
if __name__ == "__main__":
# Assume driver is already set up with stealth options
from your_stealth_setup import launch_stealth_browser # hypothetical import
driver = launch_stealth_browser()
driver.get("https://example.com/page-that-may-show-captcha")
success = scrape_with_captcha_fallback(driver)
if not success:
print("Failed to bypass CAPTCHA. Consider rotating identity or proxy.")
driver.quit()
统计 CAPTCHA 出现率与失败情况
即使是设计得再周全的 Selenium 脚本,尤其是在大规模运行时,也难免会遇到 CAPTCHA 验证的困扰,因此关注失败率非常重要。通过追踪这些数据,可以不断优化重试逻辑,找出当前方案存在的薄弱环节。
下面这段代码会记录每次请求的信息:时间戳、URL、是否出现 CAPTCHA,以及若出现的话,解决服务是否成功或是否报错,所有数据都会存储在一个 CSV 文件中,便于后续查看分析。
import csv
import time
import random
from datetime import datetime
class CAPTCHAMetrics:
def __init__(self, filename="captcha_metrics.csv"):
self.filename = filename
self.stats = {
"total_requests": 0,
"captcha_encountered": 0,
"captcha_solved": 0,
"captcha_failed": 0,
"other_errors": 0
}
# Write header if file doesn't exist
try:
with open(self.filename, 'x') as f:
writer = csv.writer(f)
writer.writerow(["timestamp", "url", "captcha_detected", "solver_used",
"solver_success", "time_to_solve_sec", "error_message"])
except FileExistsError:
pass
def log_request(self, url, captcha_detected=False, solver_used=False,
solver_success=False, time_to_solve=None, error=None):
self.stats["total_requests"] += 1
if captcha_detected:
self.stats["captcha_encountered"] += 1
if solver_success:
self.stats["captcha_solved"] += 1
else:
self.stats["captcha_failed"] += 1
if error:
self.stats["other_errors"] += 1
with open(self.filename, 'a', newline='') as f:
writer = csv.writer(f)
writer.writerow([
datetime.now().isoformat(),
url,
captcha_detected,
solver_used,
solver_success,
time_to_solve if time_to_solve else "",
error if error else ""
])
def get_captcha_rate(self):
if self.stats["total_requests"] == 0:
return 0
return (self.stats["captcha_encountered"] / self.stats["total_requests"]) * 100
def print_summary(self):
print("\n=== CAPTCHA Metrics Summary ===")
print(f"Total requests: {self.stats['total_requests']}")
print(f"CAPTCHA encountered: {self.stats['captcha_encountered']}")
print(f"CAPTCHA solved: {self.stats['captcha_solved']}")
print(f"CAPTCHA failed: {self.stats['captcha_failed']}")
print(f"Other errors: {self.stats['other_errors']}")
print(f"CAPTCHA rate: {self.get_captcha_rate():.2f}%")
if self.stats['captcha_encountered'] > 0:
solve_rate = (self.stats['captcha_solved'] / self.stats['captcha_encountered']) * 100
print(f"Solve success rate: {solve_rate:.2f}%")
print("=============================\n")
Selenium CAPTCHA 常见问题排查
CAPTCHA 仅在无头模式下出现
对于关键网站,应保持有头模式运行;如果必须使用无头模式,请使用 --headless=new(Chrome 109 及以上版本),并加入 Xvfb 等虚拟显示,同时注入 CDP 脚本以修改 WebGL 和 Canvas 指纹。
请求量增加后 CAPTCHA 出现频率上升
在请求之间加入随机延迟,将任务分散到更多 IP,降低单个 IP 的请求频率,并检查 CAPTCHA 是立即出现(说明是指纹问题)还是几分钟后才出现(说明是速率限制问题)。
解决服务返回了令牌,但验证仍然失败
在浏览器开发者工具中手动解答 CAPTCHA,观察哪些字段发生变化、执行了哪些 JavaScript 函数,并在脚本中复现这些确切步骤,先在有头模式下进行测试。
代理轮换反而使情况变糟
应将 User-Agent、视口、时区、语言、WebGL 字符串等整套身份信息一起轮换,而不仅仅轮换 IP;同一 IP 与指纹组合可保持数次请求后再进行轮换。
浏览器在本地运行正常,但在生产环境中失败
在生产服务器上使用住宅代理,确保服务器已安装常见字体,并通过 Xvfb 等虚拟显示以有头模式运行,或注入 CDP 脚本来伪造缺失的 GPU 特性。
总结
用 Selenium 绕过 CAPTCHA 是一个循序渐进的过程:首先应尽量从根源上避免触发 CAPTCHA,使用 undetected-chromedriver 搭建更难被检测的浏览器环境,并加入随机等待、变化的滚动方式,以及重试失败时的指数退避等模拟真人行为的技巧。
接下来,添加代理,并确保每个 IP 地址都配有各自完整浏览器身份指纹,轮换时要整套一起换,而不仅仅轮换 IP。当其他方法都失效时,可引入验证码破解服务作为兜底方案:脚本负责检测 CAPTCHA、将其发送至 API,并注入返回的 token。
最后,要对各项数据进行跟踪:统计 CAPTCHA 出现频率与失败模式,因为没有数据,就无法判断整套方案中哪个环节需要改进。
想获取更多此类技巧?欢迎加入我们的Discord 频道。
常见问题
Selenium 能否单独绕过 CAPTCHA?
不能,Selenium 单独无法绕过 CAPTCHA。标准的 Selenium WebDriver 会话反而会触发更多 CAPTCHA,因为它会暴露 navigator.webdriver、缺失 Chrome 对象等自动化信号,因此还需要搭配其他手段。
undetected-chromedriver 能否阻止 Selenium 中出现 CAPTCHA?
它能降低 CAPTCHA 出现的概率,但无法完全阻止:undetected-chromedriver 可隐藏 cdc_ 字符串、navigator.webdriver 标记等多种自动化指纹,但行为模式与 IP 信誉有时仍会触发验证。
为什么无头模式下更容易出现 CAPTCHA?
无头模式使用 SwiftShader 等软件渲染方式,而非真实 GPU;默认窗口尺寸为 800x600,且缺少常规字体。网站能够识别这些差异,从而向无头浏览器推送更多 CAPTCHA。
适合解决 Selenium CAPTCHA 问题的代理类型是什么?
住宅代理更适合这一场景,因为其 IP 来自真实 ISP 地址段,而非数据中心 IP 段;数据中心 IP 很容易被识别为非住宅流量。移动代理同样效果不错,但费用更高,且地理定位特征有所不同。
在 Selenium 中,什么时候应该使用 CAPTCHA 破解服务?
仅在隐身配置、模拟真人行为模式和住宅代理都无法奏效时,才将破解服务作为兜底方案:不要优先依赖破解服务,因为这会增加延迟与成本,应将其保留给偶尔突破主要防护的验证挑战。