Python Selenium 滑动验证码全攻略:从基础实战到精准定位全流程

JSON 2026-01-13 14:58:59 755

  在爬虫开发中,滑动验证码是最常见的反爬手段之一,广泛应用于登录验证、数据爬取权限校验等场景。传统的 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 新手必备:定位器获取方法

代码中“滑块定位器”需根据目标网站调整,获取方法如下:

  1. 打开目标页面,按 F12 打开开发者工具;
  2. 点击工具左上角“选择元素”按钮(箭头图标),点击页面滑块;
  3. 右键开发者工具中定位到的 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 定位器使用核心提示

  1. 有效性验证:拿到定位器后,可通过工具类的 _get_element 方法测试,避免直接使用导致报错;
  2. 动态 class 处理:若 class 含随机字符(如 geetest_1234_bg),用模糊匹配:XPath 用 contains(@class, 'geetest_canvas'),CSS 用[class*='geetest_canvas'];
  3. 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 脚本使用步骤(傻瓜式操作)

  1. 修改运行入口的 TARGET_URL 为目标验证码页面 URL;
  2. 修改USE_PRESET 为对应平台(如“腾讯防水墙”),或填写自定义定位器;
  3. 直接运行脚本,控制台将输出彩色检测结果,无效定位器会自动给出修复建议;
  4. 检测通过的定位器,直接复制到 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 拉黑,提前规避即可提升开发效率。

进阶扩展方向

  1. 接入打码平台:对于极验 4.0、腾讯防水墙等复杂验证码,可集成超级鹰、云打码等平台,实现全自动识别;
  2. 多线程/异步:结合 concurrent.futures 实现多线程爬取,提升批量数据获取效率;
  3. 滑块宽度自适应:通过获取滑块元素宽度,动态修正滑动距离,适配不同尺寸滑块;
  4. 行为日志分析:记录滑动轨迹与验证结果,通过数据分析优化轨迹生成算法,进一步提升通过率。

本文所有代码均经过实测,可直接应用于实战开发。若遇到特定平台的验证码问题,可根据目标 URL 调整定位器,或结合检测脚本生成精准定位表达式,快速突破滑动验证码限制。

版权所属:SO JSON在线解析

原文地址:https://www.sojson.com/blog/561.html

转载时必须以链接形式注明原始出处及本声明。

本文主题:

如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。

关于作者
一个低调而闷骚的男人。
相关文章
Java生成验证码合集(二)GIF版,Java生成验证码
Java生成验证码合集(一)简单版
python基础代码示例(可免费复制)
python基础代码示例(可免费复制)
2019年 中秋节、国庆节放假通知来了,拼假攻略
天气预报 API,7天天气预报API,信息,高精准天气API 接口说明
Python元组剖析
域名备案注意事项,网站域名ICP备案快速通过攻略【干货分享】
Python print() 函数
Shiro 权限设计,RBAC3基础上增加客服设计
最新文章
文件上传漏洞与防御 4058
前端构建工具选型指南:Webpack、Vite、Rollup、esbuild 深度对比 1444
物联网时代2026年时序数据库选型指南 1151
SaaS行业面临AI挑战:从“无限复用”到“灵活适应” 1269
神经网络:从构造到模型训练全链路解析 1168
一文吃透 Redis 核心存储结构:ziplist、listpack 与哈希表扩容 / 并发查询 1593
Linux sudo提权完整指南:从基础用法到生产级安全配置 691
XSS 和 CSRF 的本质区别及开发防御全解析 772
JVM垃圾回收(GC)全维度解析:从原理到调优实战 813
Linux动静态库与ELF加载全解析:从实操制作到底层原理 912
最热文章
免费天气API,天气JSON API,不限次数获取十五天的天气预报 783114
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 711464
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 679993
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 562673
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 512621
Elasticsearch教程(四) elasticsearch head 插件安装和使用 484794
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 302947
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 247433
Elasticsearch教程(一),全程直播(小白级别) 233097
谈谈斐讯路由器劫持,你用斐讯路由器,你需要知道的事情 228329
支付扫码

所有赞助/开支都讲公开明细,用于网站维护:赞助名单查看

查看我的收藏

正在加载... ...