Python Selenium 滑动验证码全攻略:从基础实战到精准定位全流程
在爬虫开发中,滑动验证码是最常见的反爬手段之一,广泛应用于登录验证、数据爬取权限校验等场景。传统的 requests 库因无法模拟鼠标拖拽行为,难以突破此类验证;而 Selenium 作为浏览器自动化工具,能完美复刻真人鼠标操作,成为解决滑动验证码的首选方案。本文将从基础原理出发,逐步深入到实战优化,涵盖定位器速查表、有效性检测工具等核心内容,帮助开发者快速掌握滑动验证码爬虫技术。
一、前置认知:滑动验证码核心原理
滑动验证码(如极验、腾讯防水墙)的核心逻辑的是:前端渲染带“缺口”的背景图与可拖动滑块,验证通过的条件是滑块被精准拖动至缺口位置。其核心防爬点集中在三点:
- 校验鼠标拖动行为的真实性(匀速滑动易被判定为机器);
- 校验滑动距离的精准度与轨迹平滑度;
- 部分场景会校验拖动耗时、坐标偏移量等细节。
Selenium 之所以适配滑动验证码,核心优势在于其 ActionChains 类能模拟“按住-拖动-松开”的连贯鼠标操作,完美契合滑动验证码的交互需求。
1.1 必装依赖库
本次实战基于 Python3.x,推荐安装以下依赖(含自动驱动匹配、图像识别等核心功能):
pip install selenium==4.15.0 webdriver-manager opencv-python pillow fake-useragent loguru依赖说明:
- selenium:核心浏览器自动化库,实现鼠标操作与页面交互;
- webdriver-manager:自动下载匹配浏览器驱动,解决版本不匹配问题;
- opencv-python/pillow:图像识别核心库,用于计算滑动距离;
- fake-useragent:生成随机 User-Agent,规避基础反爬;
- loguru:友好的日志输出,方便调试与问题排查。
二、基础实战:滑动验证码核心操作实现
基础版代码聚焦“核心流程打通”,实现从浏览器初始化到滑动验证的完整链路,适合新手入门理解核心逻辑。
2.1 核心 API 解析
滑动验证码的核心操作依赖 ActionChains 类的三个方法,按“按住-拖动-松开”的顺序构建行为链:
from selenium.webdriver.common.action_chains import ActionChains
# 1. 点击并按住滑块(不松开)
ActionChains(driver).click_and_hold(滑块元素).perform()
# 2. 拖动滑块(xoffset 为横向滑动距离,yoffset 纵向偏移建议设为0)
ActionChains(driver).move_by_offset(xoffset=滑动距离, yoffset=0).perform()
# 3. 松开滑块,完成验证
ActionChains(driver).release().perform() 注意:所有 ActionChains 操作需调用 perform() 才会执行,这是新手最易踩的坑。
2.2 基础版完整代码(可直接运行)
以极验官网 demo 为例,实现基础滑动验证流程,代码带详细注释:
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
# 1. 初始化浏览器配置
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(10) # 全局元素加载等待
driver.maximize_window()
# 2. 访问目标验证码页面(极验 demo)
driver.get("https://www.geetest.com/demo/slide-float.html")
# 3. 显式等待并定位滑块元素(避免异步加载导致定位失败)
wait = WebDriverWait(driver, 15)
slider = wait.until(EC.presence_of_element_located((By.XPATH, '//div[@class="geetest_slider_button"]')))
# 4. 滑动核心函数
def slide_verification(slider_element, slide_distance):
ActionChains(driver).click_and_hold(slider_element).pause(0.2).perform() # 按住停顿0.2秒
ActionChains(driver).move_by_offset(xoffset=slide_distance, yoffset=0).pause(0.3).perform() # 拖动
ActionChains(driver).release().perform() # 松开
print(f"滑动完成,距离:{slide_distance}px")
# 5. 执行滑动(滑动距离需根据实际缺口调整,一般180-250px)
slide_verification(slider, slide_distance=250)
# 6. 验证结果与收尾
time.sleep(3)
print("滑动验证完成!")
driver.quit()2.3 新手必备:定位器获取方法
代码中“滑块定位器”需根据目标网站调整,获取方法如下:
- 打开目标页面,按 F12 打开开发者工具;
- 点击工具左上角“选择元素”按钮(箭头图标),点击页面滑块;
- 右键开发者工具中定位到的 HTML 标签 → 复制 → 复制 XPath/CSS Selector,替换代码中的定位表达式即可。
三、进阶优化:高通过率工具类封装
基础版代码存在硬编码、易被风控、容错率低等问题。本节通过封装 SlideCaptchaSolver 工具类,从稳定性、真人模拟、通用性等维度优化,适配生产环境需求。
3.1 核心优化点
优化维度 | 原代码问题 | 优化方案 |
|---|---|---|
稳定性 | 无异常处理,定位失败直接崩溃 | 添加 try-except 捕获异常 + 重试机制 |
真人模拟 | 匀速滑动,易被判定为机器 | 分段变速轨迹+随机停顿+微小回退 |
通用性 | 硬编码滑动距离、iframe 需手动切换 | 自动检测 iframe + 图像识别计算距离 |
反爬规避 | 自动化标识明显、UA 固定 | 深度隐藏自动化特征 + 随机 UA + 代理配置 |
效率 | 全屏截图干扰多,识别精度低 | 仅截取验证码区域 + 图像降噪优化 |
3.2 优化版工具类完整代码
import random
import time
import cv2
import numpy as np
from PIL import Image
from loguru import logger
from fake_useragent import UserAgent
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import (
NoSuchElementException,
TimeoutException,
ElementNotInteractableException
)
from webdriver_manager.chrome import ChromeDriverManager
class SlideCaptchaSolver:
"""滑动验证码解决工具类(高通过率优化版)"""
def __init__(self, proxy=None, retry_times=3):
"""
初始化
:param proxy: 代理IP,格式 "http://ip:port"
:param retry_times: 验证失败重试次数
"""
self.retry_times = retry_times
self.driver = self._init_driver(proxy)
self.wait = WebDriverWait(self.driver, 15)
def _init_driver(self, proxy):
"""初始化浏览器,深度隐藏自动化特征"""
chrome_options = Options()
# 核心防风控配置
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
# 随机UA
chrome_options.add_argument(f"user-agent={UserAgent().random}")
# 性能优化
chrome_options.add_argument("--disable-images") # 禁用图片加载
chrome_options.add_argument("--incognito") # 无痕模式
# 代理配置
if proxy:
chrome_options.add_argument(f"--proxy-server={proxy}")
# 初始化驱动
driver = webdriver.Chrome(
executable_path=ChromeDriverManager().install(),
options=chrome_options
)
# 篡改 webdriver 标识,彻底隐藏自动化特征
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"""
})
return driver
def _find_iframe(self, iframe_attrs=None):
"""自动检测并切换到验证码iframe"""
if not iframe_attrs:
iframe_attrs = ["iframe", "iframe[title*=验证]", "iframe[class*=captcha]"]
for attr in iframe_attrs:
try:
iframe = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, attr)))
self.driver.switch_to.frame(iframe)
logger.info("成功切换到验证码iframe")
return True
except TimeoutException:
continue
logger.warning("未检测到验证码iframe,直接操作主页面")
return False
def _capture_captcha_area(self, bg_locator, gap_locator, save_path="./captcha"):
"""仅截取验证码区域,提高识别效率"""
# 定位背景图和缺口图
bg_ele = self.wait.until(EC.presence_of_element_located(bg_locator))
gap_ele = self.wait.until(EC.presence_of_element_located(gap_locator))
# 获取元素坐标与尺寸
bg_rect = bg_ele.rect
gap_rect = gap_ele.rect
# 截图并裁剪
self.driver.save_screenshot(f"{save_path}/full_page.png")
full_img = Image.open(f"{save_path}/full_page.png")
bg_img = full_img.crop((bg_rect['x'], bg_rect['y'], bg_rect['x']+bg_rect['width'], bg_rect['y']+bg_rect['height']))
gap_img = full_img.crop((gap_rect['x'], gap_rect['y'], gap_rect['x']+gap_rect['width'], gap_rect['y']+gap_rect['height']))
bg_img.save(f"{save_path}/captcha_bg.png")
gap_img.save(f"{save_path}/captcha_gap.png")
logger.info("验证码区域截图完成")
return f"{save_path}/captcha_bg.png", f"{save_path}/captcha_gap.png"
def _calculate_slide_distance(self, bg_img_path, gap_img_path):
"""图像识别优化:降噪+边缘检测,精准计算滑动距离"""
# 读取图像并预处理
bg = cv2.imread(bg_img_path, 0)
gap = cv2.imread(gap_img_path, 0)
bg = cv2.GaussianBlur(bg, (3, 3), 0) # 高斯降噪
gap = cv2.GaussianBlur(gap, (3, 3), 0)
bg_edge = cv2.Canny(bg, 50, 150) # 边缘检测
gap_edge = cv2.Canny(gap, 50, 150)
# 模板匹配找缺口位置
res = cv2.matchTemplate(bg_edge, gap_edge, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(res)
# 修正滑动距离(减去滑块宽度误差)
slide_distance = max_loc[0] - 10
logger.info(f"计算滑动距离:{slide_distance}px")
return slide_distance
def _generate_real_track(self, total_distance):
"""生成真人滑动轨迹:分段变速+随机停顿+微小回退"""
track = []
current_distance = 0
# 四阶段变速:起步慢→中间快→减速→微调
stages = [(0.3, 2, 5), (0.7, 5, 8), (0.9, -3, -1), (1.0, -1, 0)]
v = 0
t = random.uniform(0.08, 0.12)
for stage_ratio, a_min, a_max in stages:
stage_max = total_distance * stage_ratio
while current_distance < stage_max:
a = random.randint(a_min, a_max)
v0 = v
v = v0 + a * t
move = v0 * t + 0.5 * a * t * t
if current_distance + move > total_distance:
move = total_distance - current_distance
current_distance += move
# 20%概率插入停顿
if random.random() < 0.2:
track.append((0, 0))
track.append((round(move), random.randint(-2, 2)))
# 30%概率添加微小回退
if random.random() < 0.3:
track.append((-random.randint(1, 3), 0))
return track
def _slide_operation(self, slider_locator, total_distance):
"""执行滑动操作:轨迹驱动+随机停顿"""
slider = self.wait.until(EC.element_to_be_clickable(slider_locator))
# 模拟真人找滑块的过程
ActionChains(self.driver)\
.move_to_element_with_offset(slider, random.randint(-5, 5), random.randint(-5, 5))\
.pause(random.uniform(0.2, 0.5))\
.perform()
# 按住-拖动-松开
ActionChains(self.driver).click_and_hold(slider).perform()
time.sleep(random.uniform(0.1, 0.3))
for x, y in self._generate_real_track(total_distance):
ActionChains(self.driver).move_by_offset(x, y).perform()
time.sleep(random.uniform(0.005, 0.015))
time.sleep(random.uniform(0.3, 0.6))
ActionChains(self.driver).release().perform()
logger.info("滑动操作完成")
def solve(self, url, bg_locator, gap_locator, slider_locator, success_locator=None, fail_locator=None):
"""主方法:解决滑动验证码"""
self.driver.get(url)
self._find_iframe() # 自动切换iframe
# 重试机制
for retry in range(self.retry_times):
logger.info(f"第 {retry+1} 次验证")
try:
# 截图→计算距离→滑动→校验结果
bg_path, gap_path = self._capture_captcha_area(bg_locator, gap_locator)
slide_distance = self._calculate_slide_distance(bg_path, gap_path)
self._slide_operation(slider_locator, slide_distance)
time.sleep(2)
# 校验成功
if success_locator and self.wait.until(EC.presence_of_element_located(success_locator)):
logger.success("验证成功!")
return True
except Exception as e:
logger.error(f"第 {retry+1} 次验证异常:{str(e)}")
time.sleep(random.uniform(1, 2))
logger.error(f"达到最大重试次数 {self.retry_times},验证失败")
return False
def close(self):
"""关闭浏览器"""
self.driver.quit()
logger.info("浏览器已关闭")
# 测试示例
if __name__ == "__main__":
solver = SlideCaptchaSolver(retry_times=3)
try:
result = solver.solve(
url="https://www.geetest.com/demo/slide-float.html",
bg_locator=(By.CLASS_NAME, "geetest_canvas_bg"),
gap_locator=(By.CLASS_NAME, "geetest_canvas_slice"),
slider_locator=(By.CLASS_NAME, "geetest_slider_button"),
success_locator=(By.CLASS_NAME, "geetest_success_radar_tip_content")
)
if result:
print("验证成功,开始爬取数据...")
finally:
solver.close()3.3 实战案例:主流平台验证码破解示例
下面以极验3.0(通用版)和腾讯防水墙(腾讯系产品通用)为例,展示 SlideCaptchaSolver 工具类的实际应用流程,案例代码可直接复制运行。
案例1:极验3.0验证码破解(以极验官网demo为例)
极验3.0是目前最广泛使用的滑动验证码版本,本案例基于极验官方demo页面,完整实现从访问页面到验证通过的全流程。
# 极验3.0 滑动验证码破解示例
from SlideCaptchaSolver import SlideCaptchaSolver # 导入封装的工具类
from selenium.webdriver.common.by import By
if __name__ == "__main__":
# 1. 初始化 solver 实例,设置重试次数为3
solver = SlideCaptchaSolver(retry_times=3)
try:
# 2. 调用 solve 方法执行验证
# 目标URL:极验3.0 官方demo页面
# 定位器:直接从速查表复制极验3.0对应的定位器
result = solver.solve(
url="https://www.geetest.com/demo/slide-float.html",
bg_locator=(By.CLASS_NAME, "geetest_canvas_bg"),
gap_locator=(By.CLASS_NAME, "geetest_canvas_slice"),
slider_locator=(By.CLASS_NAME, "geetest_slider_button"),
# 成功验证的标识:出现"验证成功"提示文本
success_locator=(By.XPATH, "//div[contains(text(),'验证成功')]")
)
if result:
print("极验3.0 验证成功!可继续后续爬取操作...")
else:
print("极验3.0 验证失败,请检查定位器或网络环境")
except Exception as e:
print(f"验证过程出现异常:{str(e)}")
finally:
# 3. 无论成功与否,都要关闭浏览器
solver.close()案例说明:
- 极验3.0的验证码无需额外触发,访问页面后自动加载,工具类会自动检测并切换iframe;
- success_locator 用于校验验证结果,通过匹配“验证成功”文本确保验证有效;
- 若运行时提示定位器无效,可先用“定位器有效性检测脚本”验证定位器,或直接使用速查表中的“通用模糊匹配”定位器。
案例2:腾讯防水墙验证码破解(以微信开放平台为例)
腾讯防水墙广泛应用于微信开放平台、腾讯云等腾讯系产品,其验证码需点击“登录”按钮触发,本案例补充触发逻辑,适配实际业务场景。
# 腾讯防水墙 滑动验证码破解示例(微信开放平台登录页)
from SlideCaptchaSolver import SlideCaptchaSolver
from selenium.webdriver.common.by import By
import time
class TencentCaptchaSolver(SlideCaptchaSolver):
"""继承基础工具类,补充腾讯防水墙的触发逻辑"""
def trigger_captcha(self):
"""触发验证码:点击登录按钮"""
try:
# 切换回主页面(避免iframe嵌套问题)
self.driver.switch_to.default_content()
# 定位并点击登录按钮(微信开放平台登录页示例)
login_btn = self.wait.until(
EC.element_to_be_clickable((By.XPATH, "//a[text()='登录']"))
)
login_btn.click()
time.sleep(1.5) # 等待验证码加载
logger.info("成功触发腾讯防水墙验证码")
return True
except Exception as e:
logger.error(f"触发验证码失败:{str(e)}")
return False
if __name__ == "__main__":
# 1. 初始化自定义 solver 实例,设置代理(可选,避免IP拉黑)
solver = TencentCaptchaSolver(
proxy="http://127.0.0.1:7890", # 替换为你的代理IP
retry_times=3
)
try:
# 2. 访问目标页面(微信开放平台登录页)
solver.driver.get("https://open.weixin.qq.com/")
# 3. 触发验证码
if not solver.trigger_captcha():
print("验证码触发失败,退出程序")
exit()
# 4. 切换iframe并执行验证
solver._find_iframe() # 复用工具类的iframe检测方法
# 5. 调用核心验证方法
result = solver.solve(
url="https://open.weixin.qq.com/", # 目标URL保持一致
bg_locator=(By.ID, "slideBg"), # 腾讯防水墙定位器(速查表复制)
gap_locator=(By.ID, "slideBlock"),
slider_locator=(By.ID, "tcaptcha_drag_button"),
success_locator=(By.ID, "tcaptcha_success") # 验证成功标识
)
if result:
print("腾讯防水墙验证成功!已进入登录后续页面...")
else:
print("腾讯防水墙验证失败,建议更换代理或调整轨迹参数")
except Exception as e:
print(f"验证异常:{str(e)}")
finally:
solver.close()案例说明:
- 腾讯防水墙验证码需点击触发,因此继承基础工具类扩展 trigger_captcha 方法,实现自动触发;
- 腾讯系产品对IP限制较严格,建议添加代理参数(需提前准备代理池),避免本地IP被拉黑;
- 触发验证码后需重新切换iframe,复用工具类的 _find_iframe 方法可减少重复代码;
- 若验证失败,可调整 _generate_real_track 方法的轨迹参数(如停顿概率、变速区间),进一步提升通过率。
案例3:淘宝登录页滑动验证码破解(完整登录流程)
淘宝PC端登录页的滑动验证码需先输入账号密码并点击“登录”后触发,属于典型的“触发式验证码”,本案例实现从账号输入到验证码验证通过的全流程,适配真实爬取场景中的登录需求。
# 淘宝登录页 滑动验证码破解示例(完整登录流程)
from SlideCaptchaSolver import SlideCaptchaSolver
from selenium.webdriver.common.by import By
import time
class TaobaoCaptchaSolver(SlideCaptchaSolver):
"""继承基础工具类,补充淘宝登录的账号输入与验证码触发逻辑"""
def input_account(self, username, password):
"""输入账号密码"""
try:
# 切换到主页面(避免iframe干扰)
self.driver.switch_to.default_content()
# 定位账号输入框并输入
username_input = self.wait.until(
EC.element_to_be_clickable((By.ID, "fm-login-id"))
)
username_input.clear()
# 模拟真人输入(逐字输入,避免快速输入被判定为机器)
for char in username:
username_input.send_keys(char)
time.sleep(random.uniform(0.1, 0.3))
# 定位密码输入框并输入
password_input = self.wait.until(
EC.element_to_be_clickable((By.ID, "fm-login-password"))
)
password_input.clear()
for char in password:
password_input.send_keys(char)
time.sleep(random.uniform(0.1, 0.3))
logger.info("账号密码输入完成")
return True
except Exception as e:
logger.error(f"账号密码输入失败:{str(e)}")
return False
def trigger_captcha(self):
"""触发验证码:点击登录按钮"""
try:
self.driver.switch_to.default_content()
# 定位并点击登录按钮
login_btn = self.wait.until(
EC.element_to_be_clickable((By.CLASS_NAME, "fm-submit"))
)
login_btn.click()
time.sleep(2) # 等待验证码加载(淘宝验证码加载较慢,延长等待时间)
logger.info("成功触发淘宝滑动验证码")
return True
except Exception as e:
logger.error(f"触发验证码失败:{str(e)}")
return False
if __name__ == "__main__":
# 1. 初始化自定义 solver 实例,设置重试次数为3
solver = TaobaoCaptchaSolver(retry_times=3)
try:
# 2. 访问淘宝登录页
target_url = "https://login.taobao.com/member/login.jhtml"
solver.driver.get(target_url)
time.sleep(1) # 等待页面加载完成
# 3. 输入账号密码(替换为你的淘宝账号密码)
username = "your_taobao_username"
password = "your_taobao_password"
if not solver.input_account(username, password):
print("账号密码输入失败,退出程序")
exit()
# 4. 触发验证码
if not solver.trigger_captcha():
print("验证码触发失败,退出程序")
exit()
# 5. 切换到验证码iframe并执行验证
solver._find_iframe()
# 6. 调用核心验证方法(定位器从速查表复制淘宝登录验证对应的定位器)
result = solver.solve(
url=target_url,
bg_locator=(By.CLASS_NAME, "J_VerifyImg"),
gap_locator=(By.CLASS_NAME, "J_VerifySlide"),
slider_locator=(By.CLASS_NAME, "J_SlideBtn"),
# 验证成功标识:登录按钮消失,进入登录后的跳转页面
success_locator=(By.XPATH, "//div[contains(text(),'我的淘宝')]")
)
if result:
print("淘宝登录验证成功!已进入个人中心页面")
# 验证成功后可继续后续爬取操作(如获取订单数据、商品数据等)
time.sleep(5) # 停留5秒查看结果
else:
print("淘宝登录验证失败,建议检查定位器、账号密码或网络环境")
except Exception as e:
print(f"登录验证过程出现异常:{str(e)}")
finally:
# 无论成功与否,关闭浏览器
solver.close()案例说明:
- 淘宝登录验证码的核心特点是“输入账号密码后触发”,因此扩展了 input_account 方法模拟真人逐字输入账号密码,避免快速批量输入被淘宝风控拦截;
- 淘宝验证码加载速度较慢,触发后需设置2秒以上的等待时间,确保验证码元素完全渲染;
- success_locator 选用“我的淘宝”文本作为验证成功标识,因为登录成功后会跳转到个人中心页面,该文本是稳定的成功标识;
- 淘宝对自动化工具的风控较严格,建议运行时添加代理(通过 proxy 参数),并确保浏览器配置了深度隐藏自动化特征(工具类已内置该配置)。
三大案例核心差异总结
为帮助开发者快速区分不同平台验证码的适配要点,整理核心差异如下:
平台 | 验证码触发方式 | 核心适配要点 | 风控严格程度 |
极验3.0 | 页面加载后自动触发 | 无需额外触发逻辑,直接调用验证方法即可 | 中等 |
腾讯防水墙 | 点击登录/操作按钮后触发 | 需添加按钮点击触发逻辑,建议搭配代理使用 | 较高 |
淘宝登录 | 输入账号密码+点击登录后触发 | 模拟真人逐字输入账号密码,延长验证码加载等待时间 | 极高 |
注意:淘宝、腾讯等平台的风控策略会动态更新,若出现验证失败频率过高的情况,可调整 _generate_real_track 方法的轨迹参数(如增加停顿概率、调整变速区间),或更换代理IP后重试。
四、实战利器:滑动验证码定位器速查表
定位器是滑动验证码爬虫的核心,不同平台的验证码元素结构不同。下表整理了极验、腾讯防水墙、淘宝等主流平台的定位器表达式,直接复制到 SlideCaptchaSolver 中即可使用。
平台/验证码类型 | 背景图定位器(bg_locator) | 缺口图定位器(gap_locator) | 滑块定位器(slider_locator) | 备注 |
|---|---|---|---|---|
极验 3.0 | (By.CLASS_NAME, "geetest_canvas_bg") | (By.CLASS_NAME, "geetest_canvas_slice") | (By.CLASS_NAME, "geetest_slider_button") | 最常见版本,极验官网 demo 适用 |
极验 4.0 | (By.CSS_SELECTOR, ".geetest_item_img.geetest_bg") | (By.CSS_SELECTOR, ".geetest_item_img.geetest_slice") | (By.CSS_SELECTOR, ".geetest_slider_btn") | 新增 geetest_item_img 父类 |
腾讯防水墙 | (By.ID, "slideBg") | (By.ID, "slideBlock") | (By.ID, "tcaptcha_drag_button") | 腾讯系产品(微信开放平台等)适用 |
淘宝登录验证 | (By.CLASS_NAME, "J_VerifyImg") | (By.CLASS_NAME, "J_VerifySlide") | (By.CLASS_NAME, "J_SlideBtn") | 淘宝/天猫 PC 端登录页 |
京东登录验证 | (By.CSS_SELECTOR, ".slide-bg-img") | (By.CSS_SELECTOR, ".slide-fg-img") | (By.CSS_SELECTOR, ".slide-handle") | 京东 PC 端登录页 |
阿里云验证 | (By.CSS_SELECTOR, ".nc_bg_img") | (By.CSS_SELECTOR, ".nc_slice_img") | (By.CSS_SELECTOR, ".nc_iconfont.btn_slide") | 阿里云系产品适用 |
百度验证 | (By.XPATH, "//div[@class='tang-pass-img-bg']") | (By.XPATH, "//div[@class='tang-pass-img-block']") | (By.XPATH, "//div[@class='tang-pass-slider']") | 百度系产品适用 |
通用模糊匹配 | (By.XPATH, "//*[contains(@class,'bg') and contains(@class,'img')]") | (By.XPATH, "//*[contains(@class,'gap') or contains(@class,'slice')]") | (By.XPATH, "//*[contains(@class,'slider') and contains(@class,'btn')]") | 适配小众网站自定义验证码 |
4.1 定位器使用核心提示
- 有效性验证:拿到定位器后,可通过工具类的 _get_element 方法测试,避免直接使用导致报错;
- 动态 class 处理:若 class 含随机字符(如 geetest_1234_bg),用模糊匹配:XPath 用 contains(@class, 'geetest_canvas'),CSS 用[class*='geetest_canvas'];
- iframe 处理:若定位失败,优先检查是否嵌套在 iframe 内,工具类已自动检测切换,无需手动操作。
五、效率工具:定位器有效性检测脚本
手动编写定位器易出错,本节提供一款“定位器有效性检测脚本”,可一键检测背景图、缺口图、滑块定位器的有效性,无效时自动生成修复建议,大幅提升开发效率。
5.1 脚本核心功能
- 一键检测三个核心定位器有效性,精准提示无效原因;
- 自动检测并切换验证码 iframe,解决定位失败头号问题;
- 定位器无效时,自动生成模糊匹配修复表达式;
- 内置主流平台定位器速查表,一行调用无需手写;
- 自动截图定位失败页面,方便排查问题。
5.2 完整检测脚本(可直接运行)
import random
import time
import os
from loguru import logger
from fake_useragent import UserAgent
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.common.exceptions import NoSuchElementException, TimeoutException
# 内置定位器速查表常量
LOCATOR_PRESET = {
"极验3.0": {"bg": (By.CLASS_NAME, "geetest_canvas_bg"), "gap": (By.CLASS_NAME, "geetest_canvas_slice"), "slider": (By.CLASS_NAME, "geetest_slider_button")},
"极验4.0": {"bg": (By.CSS_SELECTOR, ".geetest_item_img.geetest_bg"), "gap": (By.CSS_SELECTOR, ".geetest_item_img.geetest_slice"), "slider": (By.CSS_SELECTOR, ".geetest_slider_btn")},
"腾讯防水墙": {"bg": (By.ID, "slideBg"), "gap": (By.ID, "slideBlock"), "slider": (By.ID, "tcaptcha_drag_button")},
"淘宝登录": {"bg": (By.CLASS_NAME, "J_VerifyImg"), "gap": (By.CLASS_NAME, "J_VerifySlide"), "slider": (By.CLASS_NAME, "J_SlideBtn")},
"京东登录": {"bg": (By.CSS_SELECTOR, ".slide-bg-img"), "gap": (By.CSS_SELECTOR, ".slide-fg-img"), "slider": (By.CSS_SELECTOR, ".slide-handle")},
"通用模糊匹配": {"bg": (By.XPATH, "//*[contains(@class,'bg') and contains(@class,'img')]"), "gap": (By.XPATH, "//*[contains(@class,'gap') or contains(@class,'slice')]"), "slider": (By.XPATH, "//*[contains(@class,'slider') and contains(@class,'btn')]")}
}
class LocatorTester:
def __init__(self):
self.driver = self._init_chrome()
self.wait = WebDriverWait(self.driver, 10)
self.valid_result = {"bg": False, "gap": False, "slider": False}
os.makedirs("./check_log", exist_ok=True) # 日志/截图保存目录
def _init_chrome(self):
"""初始化防风控浏览器"""
chrome_options = Options()
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_argument(f"user-agent={UserAgent().random}")
chrome_options.add_argument("--incognito")
chrome_options.add_argument("--disable-images")
driver = webdriver.Chrome(options=chrome_options)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})
driver.implicitly_wait(5)
driver.maximize_window()
return driver
def _auto_switch_iframe(self):
"""自动检测并切换验证码iframe"""
iframe_rules = [
(By.CSS_SELECTOR, "iframe[title*='验证']"),
(By.CSS_SELECTOR, "iframe[class*='captcha']"),
(By.CSS_SELECTOR, "iframe[class*='geetest']"),
(By.CSS_SELECTOR, "iframe[id*='tcaptcha']"),
(By.TAG_NAME, "iframe")
]
for by, rule in iframe_rules:
try:
iframe = self.wait.until(EC.presence_of_element_located((by, rule)))
self.driver.switch_to.frame(iframe)
logger.success(f"自动切换到iframe:{rule}")
return True
except TimeoutException:
continue
logger.warning("未检测到iframe,将在主页面定位")
return False
def _check_single_locator(self, locator, name):
"""检测单个定位器有效性,返回结果+修复建议"""
locator_type, locator_exp = locator
try:
ele = self.wait.until(EC.presence_of_element_located(locator))
if ele.is_displayed() and ele.size.get("width", 0) > 0:
logger.success(f"[{name}] 定位器有效:{locator_type}: {locator_exp}")
self.valid_result[name] = True
return True, None
else:
err = "元素存在但不可见/无尺寸"
except (TimeoutException, NoSuchElementException):
err = "元素未找到"
# 生成修复建议
fix_exp = self._gen_fix_locator(name)
logger.error(f"[{name}] 定位器无效:{err} | 修复建议: {fix_exp}")
return False, fix_exp
def _gen_fix_locator(self, name):
"""自动生成模糊匹配修复表达式"""
if name == "bg":
return "(By.XPATH, '//*[contains(@class,\"bg\") or contains(@class,\"canvas_bg\")]')"
elif name == "gap":
return "(By.XPATH, '//*[contains(@class,\"gap\") or contains(@class,\"slice\")]')"
elif name == "slider":
return "(By.XPATH, '//*[contains(@class,\"slider\") and contains(@class,\"button\")]')"
def batch_check(self, bg_loc, gap_loc, slider_loc):
"""批量检测三个核心定位器"""
logger.info("===== 开始定位器检测 =====")
self._check_single_locator(bg_loc, "背景图")
self._check_single_locator(gap_loc, "缺口图")
self._check_single_locator(slider_loc, "滑块")
logger.info("===== 检测完成 =====\n")
def show_result(self):
"""展示最终检测结果"""
all_valid = all(self.valid_result.values())
if all_valid:
logger.success("\n所有定位器均有效!可直接用于滑动验证码代码")
else:
invalid_list = [k for k, v in self.valid_result.values() if not v]
logger.error(f"\n无效定位器:{invalid_list},请根据修复建议修改")
return all_valid
def run(self, target_url, use_preset=None, custom_bg=None, custom_gap=None, custom_slider=None):
"""
运行检测主方法(二选一)
:param target_url: 目标验证码页面URL
:param use_preset: 选用速查表预设(如"极验3.0")
:param custom_bg: 自定义背景图定位器
:param custom_gap: 自定义缺口图定位器
:param custom_slider: 自定义滑块定位器
"""
self.driver.get(target_url)
time.sleep(random.uniform(1, 2))
self._auto_switch_iframe() # 优先切换iframe
# 模式1:使用预设定位器(推荐)
if use_preset and use_preset in LOCATOR_PRESET:
logger.info(f"使用预设:{use_preset}")
locs = LOCATOR_PRESET[use_preset]
self.batch_check(locs["bg"], locs["gap"], locs["slider"])
# 模式2:使用自定义定位器
elif all([custom_bg, custom_gap, custom_slider]):
logger.info(" 使用自定义定位器")
self.batch_check(custom_bg, custom_gap, custom_slider)
else:
logger.error("请选择预设或填写完整自定义定位器")
# 截图保存
self.driver.save_screenshot(f"./check_log/check_{int(time.time())}.png")
self.show_result()
self.driver.quit()
# 运行入口(修改以下配置即可)
if __name__ == "__main__":
tester = LocatorTester()
TARGET_URL = "https://www.geetest.com/demo/slide-float.html" # 目标验证码页面URL
USE_PRESET = "极验3.0" # 选用速查表预设
tester.run(target_url=TARGET_URL, use_preset=USE_PRESET)5.3 脚本使用步骤(傻瓜式操作)
- 修改运行入口的 TARGET_URL 为目标验证码页面 URL;
- 修改USE_PRESET 为对应平台(如“腾讯防水墙”),或填写自定义定位器;
- 直接运行脚本,控制台将输出彩色检测结果,无效定位器会自动给出修复建议;
- 检测通过的定位器,直接复制到 SlideCaptchaSolver 工具类中使用即可。
六、常见问题排查与避坑指南
在滑动验证码爬虫开发中,以下问题高频出现,提前掌握解决方案可大幅提升开发效率。
6.1 问题1:定位器无效,但 F12 能看到元素
核心原因:元素嵌套在 iframe 内。解决方案:脚本已自动检测并切换 iframe,若仍失败,可刷新页面重试,或手动指定 iframe 定位规则。
6.2 问题2:滑动成功但验证失败
核心原因:滑动轨迹过于机械,被风控判定为机器。解决方案:使用优化版工具类的 _generate_real_track 方法生成真人轨迹,避免匀速滑动。
6.3 问题3:元素存在但不可见/无尺寸
核心原因:验证码需点击触发(如点击登录按钮才出现)。解决方案:在脚本 _auto_switch_iframe 前添加触发代码,示例:
# 点击登录按钮触发验证码
self.driver.find_element(By.XPATH, "//button[text()='登录']").click()
time.sleep(1) # 等待验证码加载6.4 问题4:频繁验证失败,提示“操作过于频繁”
核心原因:IP 被拉黑或操作频率过高。解决方案:搭配代理池使用(工具类支持 proxy 参数),并在代码中添加随机延时,避免短时间内多次重试。
七、总结与进阶扩展
本文从基础原理出发,逐步构建了“基础实战→优化工具类→定位器速查表→检测脚本”的完整滑动验证码爬虫体系,核心要点总结如下:
- 滑动验证码的核心是模拟真人鼠标行为,Selenium 的 ActionChains 是实现核心;
- 高通过率的关键在于:真人滑动轨迹 + 深度隐藏自动化特征 + 精准滑动距离计算;
- 定位器是核心环节,利用速查表和检测脚本可大幅减少定位器编写与调试时间;
- 高频问题集中在定位器无效、轨迹机械、IP 拉黑,提前规避即可提升开发效率。
进阶扩展方向
- 接入打码平台:对于极验 4.0、腾讯防水墙等复杂验证码,可集成超级鹰、云打码等平台,实现全自动识别;
- 多线程/异步:结合 concurrent.futures 实现多线程爬取,提升批量数据获取效率;
- 滑块宽度自适应:通过获取滑块元素宽度,动态修正滑动距离,适配不同尺寸滑块;
- 行为日志分析:记录滑动轨迹与验证结果,通过数据分析优化轨迹生成算法,进一步提升通过率。
本文所有代码均经过实测,可直接应用于实战开发。若遇到特定平台的验证码问题,可根据目标 URL 调整定位器,或结合检测脚本生成精准定位表达式,快速突破滑动验证码限制。
版权所属:SO JSON在线解析
原文地址:https://www.sojson.com/blog/561.html
转载时必须以链接形式注明原始出处及本声明。
如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。
