滑动验证码自动化实战:背景提取、缺口定位与Playwright滑动模拟
一、前言
在爬虫自动化、Web端自动化测试、业务流程自动化等场景中,人机验证是保障系统安全的重要防线,也是自动化流程中最常见的"拦路虎"。极验(Geetest)作为国内领先的行为验证码厂商,其第四代自适应滑动验证码凭借动态防护、行为轨迹校验、环境检测等多重技术,成为众多网站的首选验证方案。
与传统静态验证码不同,极验滑动验证码通过"拖动滑块填充缺口"的交互方式,结合用户行为特征判断是否为人类操作,大幅提升了机器破解的难度。本文以极验官方自适应验证测试页面(https://www.geetest.com/adaptive-captcha)为实战靶场,全程聚焦技术实战与合规性,完整讲解滑动验证码背景图的精准提取、缺口坐标定位思路,以及基于Playwright的拟人化鼠标滑动模拟实现,不依赖第三方解码平台、不提供违规API接入代码,助力开发者掌握滑动验证码自动化的核心逻辑与实操方法。
本文适合具备Python基础、了解Playwright基本用法的开发者阅读,通过本文学习,可掌握滑动验证码自动化的核心流程,能够独立完成背景提取、缺口定位与滑动模拟的全流程开发,适配各类基于极验滑动验证码的自动化场景。
二、极验滑动验证码核心机制解析
2.1 极验滑动验证码的核心防护逻辑
极验第四代自适应滑动验证码摒弃了传统验证码"单纯图片识别"的防御模式,构建了"AI动态风控+行为特征校验+环境检测"的立体防护体系,其核心防护逻辑主要包括以下4点:
-
动态图片生成:后台会实时生成不同的背景图与缺口组合,每小时更新数十万组图片,通过视觉偏差、异构视差等设计,避免黑产通过图片库穷举、模板匹配等方式破解。
-
行为轨迹校验:全程采集用户拖动滑块过程中的核心数据,包括鼠标按下坐标、移动轨迹、速度变化、加速度、停留时间等,生成唯一的行为指纹,通过AI算法判断是否为人类的自然操作(机器模拟的轨迹往往过于规整,缺乏人类操作的随机性)。
-
环境风险检测:自动识别当前运行环境是否存在异常,包括Headless浏览器、模拟器、IP代理、设备信息篡改等,一旦检测到异常环境,会直接提升验证难度或拦截验证请求。
-
加密参数校验:滑动完成后,前端会生成加密参数(如w参数),并将其提交至后端,后端通过解密参数、校验轨迹合法性与图片匹配度,最终判断验证是否通过。
2.2 滑动验证码的核心组成元素
要实现滑动验证码的自动化,首先需要明确其页面结构,极验滑动验证码主要由3个核心元素组成,也是我们自动化操作的关键目标:
-
背景图(对应CSS类名:.geetest_bg):这是带有缺口的完整背景图片,缺口的位置就是滑块需要拖动到的目标位置,也是我们后续进行缺口定位的核心载体。
-
滑块图(对应CSS类名:.geetest_slice):待拖动的拼图块,其形状与背景图中的缺口完全匹配,拖动过程中会跟随鼠标移动,释放后与缺口对齐则验证成功。
-
滑动触发按钮(对应CSS类名:.geetest_btn_click):用于触发滑动验证码加载的交互按钮,点击后会加载背景图、滑块图,进入滑动验证环节。
自动化的核心目标的本质的是:精准提取背景图 → 定位缺口与滑块起始位置的横向距离 → 模拟人类的自然滑动轨迹,将滑块拖动至缺口位置,完成验证。其中,背景图的提取是整个流程的基础------无论是自主实现缺口定位,还是后续对接相关工具,都需要以提取到的背景图作为定位依据。
三、环境准备与依赖安装
3.1 核心依赖库说明
本文实战所用到的核心工具与依赖库,均为Python生态中常用的合规工具,无任何违规依赖,具体如下:
-
Playwright:微软开源的浏览器自动化工具,支持Chromium、Firefox、WebKit等浏览器,能够精准模拟鼠标点击、拖动、键盘输入等人类操作,相比Selenium,其稳定性更强、执行速度更快,且对动态页面的适配更好,是模拟滑动操作的核心工具。
-
requests:Python常用的HTTP请求库,用于发送请求下载滑动验证码的背景图片。
-
re(正则表达式):Python内置模块,用于从背景图元素的style属性中提取图片URL。
-
opencv-python + numpy(可选):用于图像处理,自主实现缺口定位(后续会详细讲解定位思路)。
3.2 依赖库安装步骤
打开终端,执行以下命令,完成所有依赖的安装:
bash
# 安装Playwright核心库
pip install playwright
# 安装Playwright对应的浏览器驱动(本文使用Chromium)
playwright install chromium
# 安装requests库(用于下载图片)
pip install requests
# 可选:安装图像处理依赖(用于自主实现缺口定位)
pip install opencv-python numpy
四、滑动验证码背景图精准提取(核心实战)
4.1 提取思路解析
极验滑动验证码的背景图,并非直接以img标签的形式加载,而是通过CSS的background-image属性嵌入到页面元素中。因此,我们无法直接通过定位img标签获取图片URL,只能通过以下步骤提取:
-
使用Playwright打开极验测试页面,触发滑动验证码加载。
-
定位到背景图对应的元素(.geetest_bg),获取其style属性值。
-
通过正则表达式,从style属性中提取background-image对应的图片URL。
-
使用requests库发送请求,下载图片并保存到本地,用于后续的缺口定位。
这一步是整个滑动验证码自动化的基础,也是最关键的步骤之一------只有精准提取到背景图,才能后续进行缺口定位,进而完成滑动模拟。
4.2 完整提取代码与详细解析
以下是背景图提取的完整代码。
python
import re
import requests
from playwright.sync_api import sync_playwright
def extract_geetest_bg_image():
"""
提取极验滑动验证码背景图并保存到本地
:return: 背景图保存路径
"""
# 启动Playwright,使用Chromium浏览器,headless=False表示显示浏览器(便于调试)
with sync_playwright() as p:
# 启动浏览器实例,headless=False用于调试,上线后可改为True(无界面运行)
browser = p.chromium.launch(headless=False)
# 创建新的浏览器上下文(隔离不同的会话)
context = browser.new_context()
# 打开新的页面
page = context.new_page()
# 访问极验滑动验证码测试页面
page.goto("https://www.geetest.com/adaptive-captcha")
# 等待页面加载完成(强制等待1秒,避免页面未加载完成导致元素定位失败)
page.wait_for_timeout(1000)
# 点击"滑动验证"标签(切换到滑动验证页面,对应CSS类名.tab-item-1)
page.click(".tab-item-1")
# 等待标签切换完成,避免元素未渲染
page.wait_for_timeout(1000)
# 点击滑动触发按钮,加载滑动验证码(背景图、滑块图)
page.click(".geetest_btn_click")
# 等待验证码加载完成(滑动验证码加载需要一定时间,等待2秒确保加载成功)
page.wait_for_timeout(2000)
# 定位背景图元素(CSS类名.geetest_bg)
ele_img = page.locator(".geetest_bg")
# 获取背景图元素的style属性值(包含background-image的URL)
img_style = ele_img.get_attribute("style")
# 使用正则表达式提取style中的图片URL
# 正则匹配规则:匹配url("xxx")格式,提取括号内的URL内容
ele_png = re.compile(r'url\("(.+?)"\)')
# 提取图片URL(findall返回列表,取第一个元素即为目标URL)
img_url = ele_png.findall(img_style)[0]
print(f"成功提取背景图URL:{img_url}")
# 下载背景图并保存到本地
try:
# 发送HTTP请求获取图片内容,设置超时时间避免请求卡住
response = requests.get(img_url, timeout=10)
# 验证请求是否成功(状态码200表示成功)
response.raise_for_status()
# 以二进制模式打开文件,写入图片内容(保存为1.png,可自行修改保存路径)
with open("1.png", "wb") as f:
f.write(response.content)
print("背景图下载完成,保存路径:./1.png")
return "./1.png"
except Exception as e:
print(f"背景图下载失败:{str(e)}")
return None
# 调用函数,执行背景图提取
if __name__ == "__main__":
extract_geetest_bg_image()
4.3 常见问题与解决方案
在背景图提取过程中,开发者可能会遇到各种问题,以下是最常见的3个问题及对应的解决方案,帮助大家快速排查问题:
-
问题1:元素定位失败(NoSuchElementException)。原因:页面加载速度过慢,或者元素选择器错误。解决方案:延长等待时间(调整page.wait_for_timeout的参数,如改为2000);通过浏览器开发者工具(F12)检查元素的CSS类名是否正确,确保.locator(".geetest_bg")能够精准定位到背景图元素。
-
问题2:正则提取不到图片URL。原因:style属性格式发生变化,正则匹配规则不适用。解决方案:打印img_style的值,查看background-image的格式(如是否为url('xxx')、无引号等),调整正则表达式(如将r'url("(.+?)")'改为r'url('(.+?))',适配无引号或单引号的情况)。
-
问题3:图片下载失败(请求超时、403禁止访问)。原因:极验对图片请求进行了Referer校验,或者IP被限制。解决方案:在requests.get中添加请求头,模拟浏览器请求(如添加Referer、User-Agent);更换IP或暂停请求,避免被反爬限制。
补充:添加请求头的示例代码(修改requests.get部分),可有效解决403禁止访问问题:
python
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://www.geetest.com/" # 必须添加Referer,模拟页面内请求
}
response = requests.get(img_url, headers=headers, timeout=10)
五、缺口定位思路(自主实现,无需第三方工具)
提取到背景图后,下一步就是定位缺口的位置------缺口的X坐标(横向坐标)是我们计算滑动距离的核心依据(纵向坐标基本固定,无需重点关注)。目前,自主实现缺口定位的思路主要有两种,分别适用于不同的场景,开发者可根据自身需求选择。
5.1 思路一:基于图像对比的缺口定位(推荐)
核心原理:极验滑动验证码的背景图,本质是"完整图片"与"缺口图片"的合成,缺口区域与周围区域的像素值存在明显差异(如亮度、灰度不同)。通过图像处理技术,对比图片的像素差异,即可定位到缺口的位置。
具体实现步骤(基于opencv-python):
-
读取下载的背景图,将其转换为灰度图(减少颜色干扰,提高定位精度)。
-
对灰度图进行高斯模糊处理,去除图片噪声,避免误判。
-
使用Canny边缘检测算法,提取图片的边缘信息(缺口区域的边缘会更加明显)。
-
通过轮廓检测,找到缺口对应的轮廓,计算轮廓的中心点X坐标,即为缺口的目标位置。
核心代码片段(仅展示定位逻辑,可自行整合到完整脚本中):
python
import cv2
import numpy as np
def find_gap_position(bg_path):
"""
基于图像对比,定位背景图中缺口的X坐标
:param bg_path: 背景图保存路径
:return: 缺口X坐标
"""
# 读取背景图
img = cv2.imread(bg_path)
# 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 高斯模糊,去除噪声(ksize为奇数,sigmaX为标准差)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
# Canny边缘检测(minVal、maxVal可根据实际图片调整)
edges = cv2.Canny(blur, 50, 150)
# 轮廓检测(cv2.RETR_EXTERNAL表示只检测最外层轮廓)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 遍历轮廓,筛选出缺口对应的轮廓(缺口轮廓面积、宽高比有明显特征)
for cnt in contours:
# 计算轮廓的 bounding rectangle(外接矩形)
x, y, w, h = cv2.boundingRect(cnt)
# 筛选条件:缺口的宽高比大致为1:1,面积在一定范围内(可根据实际图片调整)
if 30 < w < 60 and 30 < h < 60 and 0.8 < w/h < 1.2:
# 缺口的X坐标为外接矩形的中心点X坐标
gap_x = x + w // 2
print(f"成功定位缺口X坐标:{gap_x}")
return gap_x
print("缺口定位失败,请调整参数或检查图片")
return None
5.2 思路二:基于像素阈值的缺口定位(简单易实现)
核心原理:缺口区域的像素亮度通常比周围区域更亮(或更暗),通过设置一个像素阈值,遍历图片的横向像素,找到像素值突变的位置,即为缺口的起始位置。
具体实现步骤:
-
读取背景图,转换为灰度图。
-
遍历图片的每一列(横向),计算每一列的平均像素值。
-
当某一列的平均像素值与相邻列的差值超过设定阈值时,该列即为缺口的起始位置,进而计算出缺口的中心点X坐标。
这种方法的优点是代码简单、无需复杂的图像处理知识,缺点是定位精度略低于图像对比法,适用于缺口与背景差异较大的场景。
5.3 缺口定位注意事项
-
定位参数调整:无论是图像对比法还是像素阈值法,都需要根据实际下载的背景图调整参数(如Canny边缘检测的阈值、轮廓筛选的面积范围),因为极验的背景图是动态生成的,不同图片的缺口大小、亮度可能存在差异。
-
坐标校准:页面中的背景图可能存在缩放(如自适应页面),定位到的图片像素坐标,需要转换为页面的实际坐标(可通过Playwright获取背景图元素的宽高,计算缩放比例,进行坐标校准)。
-
抗干扰处理:部分背景图可能存在与缺口像素差异相似的区域,需要通过多轮筛选(如轮廓形状、面积、位置),避免误判缺口位置。
六、基于Playwright的拟人化滑动模拟(核心实战)
完成背景图提取和缺口定位后,最关键的一步就是模拟人类的鼠标滑动操作------极验的行为轨迹校验机制,对滑动轨迹的自然度要求极高,过于规整的滑动轨迹(如匀速滑动)会被判定为机器操作,导致验证失败。因此,我们需要模拟人类滑动的特征:先加速、再匀速、最后减速,同时加入轻微的横向波动,让轨迹更接近人类操作。
6.1 滑动模拟核心思路
滑动模拟的核心是"模拟鼠标的按下→移动→释放"三个动作,关键在于控制移动轨迹的自然度,具体思路如下:
-
定位滑块元素(.geetest_slice),获取滑块的起始X坐标(滑块的初始位置是固定的,可通过元素定位获取)。
-
根据缺口X坐标,计算滑块需要滑动的总距离(总距离 = 缺口X坐标 - 滑块起始X坐标 - 校准值,校准值用于修正定位误差)。
-
模拟鼠标按下滑块(鼠标左键按下,不释放)。
-
分阶段移动鼠标:第一阶段(加速),移动速度逐渐加快;第二阶段(匀速),保持稳定速度移动;第三阶段(减速),移动速度逐渐减慢,最终到达缺口位置。
-
释放鼠标左键,完成滑动操作,等待验证结果。
6.2 完整滑动模拟代码与解析
以下代码整合了背景图提取、缺口定位、滑动模拟的全流程,重点优化了滑动轨迹的自然度,加入了随机波动和速度变化,避免被极验的行为校验拦截:
python
import re
import requests
import cv2
import numpy as np
import random
from playwright.sync_api import sync_playwright
def extract_geetest_bg_image(page):
"""提取背景图并保存,复用页面实例,避免重复启动浏览器"""
# 定位背景图元素,获取style属性
ele_img = page.locator(".geetest_bg")
img_style = ele_img.get_attribute("style")
# 正则提取图片URL
ele_png = re.compile(r'url\("(.+?)"\)')
img_url = ele_png.findall(img_style)[0]
# 下载图片
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://www.geetest.com/"
}
response = requests.get(img_url, headers=headers, timeout=10)
with open("1.png", "wb") as f:
f.write(response.content)
return "./1.png"
def find_gap_position(bg_path):
"""定位缺口X坐标,基于图像对比法"""
img = cv2.imread(bg_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blur, 50, 150)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
if 30 < w < 60 and 30 < h < 60 and 0.8 < w/h < 1.2:
return x + w // 2
return None
def simulate_human_slide(page, gap_x):
"""
拟人化滑动模拟
:param page: Playwright页面实例
:param gap_x: 缺口X坐标
"""
# 定位滑块元素
slider = page.locator(".geetest_slice")
# 获取滑块的起始位置(页面坐标)
slider_bbox = slider.bounding_box()
if not slider_bbox:
print("滑块定位失败")
return
# 滑块起始X坐标(页面坐标)
start_x = slider_bbox["x"] + slider_bbox["width"] // 2
start_y = slider_bbox["y"] + slider_bbox["height"] // 2
# 计算滑动总距离(减去校准值,避免定位误差)
total_distance = gap_x - start_x - 5 # 校准值可根据实际情况调整
if total_distance <= 0:
print("滑动距离异常")
return
# 模拟鼠标按下滑块(按下后不释放)
page.mouse.move(start_x, start_y)
page.wait_for_timeout(random.randint(50, 150)) # 按下前轻微停顿,模拟人类思考
page.mouse.down()
page.wait_for_timeout(random.randint(30, 80)) # 按下后停顿,模拟人类发力准备
# 分阶段滑动:加速→匀速→减速
# 加速阶段:移动总距离的30%,速度逐渐加快
accelerate_distance = int(total_distance * 0.3)
for i in range(accelerate_distance):
# 速度逐渐增加,加入轻微横向波动
speed = random.uniform(1.5, 3.5) + i * 0.02
x = start_x + i
# 轻微纵向波动,模拟人类操作的不稳定性
y = start_y + random.randint(-2, 2)
page.mouse.move(x, y, steps=1)
page.wait_for_timeout(int(10 / speed))
# 匀速阶段:移动总距离的50%,速度保持稳定
uniform_distance = int(total_distance * 0.5)
for i in range(uniform_distance):
speed = random.uniform(2.5, 3.5)
x = start_x + accelerate_distance + i
y = start_y + random.randint(-2, 2)
page.mouse.move(x, y, steps=1)
page.wait_for_timeout(int(10 / speed))
# 减速阶段:移动剩余距离,速度逐渐减慢
decelerate_distance = total_distance - accelerate_distance - uniform_distance
for i in range(decelerate_distance):
speed = random.uniform(1.0, 2.5) - i * 0.03
if speed < 0.5:
speed = 0.5
x = start_x + accelerate_distance + uniform_distance + i
y = start_y + random.randint(-2, 2)
page.mouse.move(x, y, steps=1)
page.wait_for_timeout(int(10 / speed))
# 释放鼠标,完成滑动
page.wait_for_timeout(random.randint(30, 80))
page.mouse.up()
print("滑动模拟完成,等待验证结果")
# 等待验证结果(可根据页面提示调整等待时间)
page.wait_for_timeout(2000)
# 主函数,整合全流程
if __name__ == "__main__":
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://www.geetest.com/adaptive-captcha")
page.wait_for_timeout(1000)
page.click(".tab-item-1")
page.wait_for_timeout(1000)
page.click(".geetest_btn_click")
page.wait_for_timeout(2000)
# 1. 提取背景图
bg_path = extract_geetest_bg_image(page)
if not bg_path:
browser.close()
exit()
# 2. 定位缺口X坐标
gap_x = find_gap_position(bg_path)
if not gap_x:
browser.close()
exit()
# 3. 模拟拟人化滑动
simulate_human_slide(page, gap_x)
# 关闭浏览器
browser.close()
6.3 滑动模拟关键优化点(提升验证成功率)
极验的行为轨迹校验是自动化验证的核心难点,以下几个优化点,能够大幅提升滑动验证的成功率,避免被判定为机器操作:
-
加入随机停顿:在鼠标按下前、按下后、释放前加入随机时长的停顿(50-150ms),模拟人类操作的思考和发力过程,避免操作过于连贯。
-
分阶段速度变化:严格按照"加速→匀速→减速"的规律滑动,人类拖动滑块时,速度不会保持恒定,而是有一个自然的加速和减速过程。
-
轻微波动:在滑动过程中,加入轻微的纵向波动(±2像素),模拟人类操作时的手部抖动,避免轨迹过于规整。
-
速度随机化:每个阶段的滑动速度都加入随机值,避免每次滑动的速度完全一致,进一步提升轨迹的自然度。
-
坐标校准:由于背景图可能存在缩放,定位到的缺口像素坐标需要根据页面元素的实际大小进行校准,避免滑动距离偏差。
七、实战优化与常见问题排查
7.1 整体流程优化建议
在实际项目中,为了提升自动化的稳定性和成功率,可对上述流程进行以下优化:
-
复用浏览器实例:避免每次提取背景图、滑动模拟都重新启动浏览器,减少资源占用,提升执行速度。
-
增加异常重试机制:当缺口定位失败、滑动验证失败时,自动刷新页面、重新触发验证,重试次数可设置为3-5次。
-
参数动态调整:根据不同的页面环境、背景图,动态调整缺口定位参数(如Canny阈值)和滑动速度参数,适配不同场景。
-
环境伪装:通过Playwright设置浏览器的User-Agent、语言、分辨率等信息,模拟真实用户环境,避免被极验检测到自动化环境。
7.2 常见问题与排查方案
在实战过程中,除了背景图提取的问题外,还可能遇到以下问题,结合实际经验给出排查方案:
-
问题1:滑动后验证失败(提示"拖动滑块有误,请重新拖动")。原因:滑动轨迹不自然、缺口定位偏差、滑动速度过快/过慢。解决方案:调整滑动轨迹的速度变化和波动范围;重新校准缺口定位参数,修正滑动距离;增加停顿时间,避免操作过于急促。
-
问题2:滑块定位失败(bounding_box()返回None)。原因:滑块元素未加载完成,或者元素选择器错误。解决方案:延长等待时间,确保滑块加载完成;通过浏览器开发者工具检查滑块的CSS类名是否正确。
-
问题3:多次滑动均失败,被判定为机器操作。原因:环境被检测到(如Headless模式)、滑动轨迹过于固定。解决方案:将headless改为False,使用可视浏览器;增加滑动参数的随机性,避免每次滑动轨迹完全一致;设置浏览器的真实分辨率和User-Agent。
-
问题4:背景图下载后无法定位缺口。原因:图片下载不完整、图片格式异常。解决方案:检查图片下载是否完整(打开图片确认);在requests.get中添加超时处理和请求头,确保下载的图片完整可用。
八、总结与拓展
本文以极验滑动验证码为实战案例,完整讲解了滑动验证码自动化的核心流程------背景图提取、缺口定位、Playwright拟人化滑动模拟,全程聚焦自主实现,不依赖第三方解码平台和违规API,兼顾技术实用性与合规性。
滑动验证码自动化的核心难点,不在于代码的编写,而在于对极验防护机制的理解和行为轨迹的模拟。通过本文的学习,开发者可以掌握:如何从动态页面中提取核心图片资源、如何通过图像处理技术自主定位缺口、如何模拟人类的自然滑动轨迹,从而实现滑动验证码的自动化突破。
拓展方向:
-
多场景适配:将本文的代码适配到不同网站的极验滑动验证码,调整元素选择器、定位参数和滑动参数,实现通用化。
-
轨迹优化:结合机器学习,分析人类滑动轨迹的特征,生成更接近人类的滑动轨迹,进一步提升验证成功率。
-
多验证类型适配:极验还有点选、图文验证等类型,可基于本文的思路,拓展其他类型验证码的自动化实现。
需要注意的是,滑动验证码自动化技术仅用于合法的自动化测试、业务流程自动化等场景,严禁用于爬虫攻击、恶意破解等违规违法活动。开发者应遵守网站的 robots 协议和相关法律法规,尊重网站的安全防护机制,共同维护网络环境的安全与稳定。
关注我,了解更多爬虫知识和实战经验~~