基于UiBot的滑动验证码检测实例

滑动验证码的挑战

在RPA自动化过程中,滑动验证码是最常见也是最具挑战性的反爬机制之一。传统的OCR识别方法对滑动验证码往往束手无策,因为滑动验证码不仅包含文字识别,还涉及图像处理、轨迹模拟等多个技术环节。本文将详细介绍如何使用UiBot结合Python实现滑动验证码的智能检测和自动破解。
滑动验证码示例

滑动验证码原理分析

验证码类型识别

滑动验证码主要分为以下几种类型:

  1. 拼图式滑动验证码:需要将拼图块拖动到正确位置
  2. 缺口式滑动验证码:识别图片中的缺口位置并拖动滑块
  3. 旋转式滑动验证码:需要旋转图片到正确角度
  4. 行为式滑动验证码:模拟人类滑动轨迹

技术实现难点

  • 图像识别精度:需要精确识别验证码图片中的关键元素
  • 轨迹模拟:需要模拟人类自然的滑动轨迹
  • 反检测机制:需要绕过网站的反自动化检测
  • 性能优化:需要在保证成功率的同时提高处理速度

技术方案设计

整体架构

1
2
3
4
5
6
7
8
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│ UiBot流程 │ │ Python服务 │ │ 浏览器页面 │
│ │ │ │ │ │
│ 1. 页面截图 │───►│ 1. 图像分析 │───►│ 1. 显示验证码 │
│ 2. 调用Python │ │ 2. 缺口识别 │ │ 2. 验证轨迹 │
│ 3. 执行滑动 │ │ 3. 轨迹生成 │ │ 3. 返回结果 │
│ 4. 结果验证 │◄───│ 4. 返回坐标 │◄───│ │
└─────────────────┘ └─────────────────┘ └─────────────────┘

核心模块

1. 图像处理模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import cv2
import numpy as np
from PIL import Image
import requests
from io import BytesIO

class CaptchaProcessor:
def __init__(self):
self.image_cache = {}
self.template_cache = {}

def download_image(self, url):
"""下载验证码图片"""
response = requests.get(url)
image = Image.open(BytesIO(response.content))
return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)

def preprocess_image(self, image):
"""图像预处理"""
# 灰度化
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 高斯模糊降噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)

# 边缘检测
edges = cv2.Canny(blurred, 50, 150)

return edges

def find_template_match(self, background, template):
"""模板匹配寻找缺口位置"""
# 使用模板匹配算法
result = cv2.matchTemplate(background, template, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)

# 设置匹配阈值
if max_val > 0.6:
return max_loc
else:
return None

def detect_gap_position(self, background_path, slider_path):
"""检测缺口位置"""
# 读取背景图和滑块图
background = cv2.imread(background_path)
slider = cv2.imread(slider_path)

# 预处理图像
bg_processed = self.preprocess_image(background)
slider_processed = self.preprocess_image(slider)

# 寻找缺口位置
gap_position = self.find_template_match(bg_processed, slider_processed)

if gap_position:
return {
'x': gap_position[0],
'y': gap_position[1],
'confidence': 0.85,
'method': 'template_matching'
}
else:
# 使用边缘检测法作为备选方案
return self.detect_gap_by_edges(background)

def detect_gap_by_edges(self, image):
"""基于边缘检测的缺口识别"""
# 转换为灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 边缘检测
edges = cv2.Canny(gray, 50, 150)

# 寻找轮廓
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 分析轮廓找到缺口
for contour in contours:
area = cv2.contourArea(contour)
x, y, w, h = cv2.boundingRect(contour)

# 根据缺口特征筛选
if 50 < area < 5000 and 20 < w < 100 and 20 < h < 100:
return {
'x': x,
'y': y,
'width': w,
'height': h,
'method': 'edge_detection'
}

return None

2. 轨迹生成模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import random
import math
import time

class TrajectoryGenerator:
def __init__(self):
self.tracks = []

def generate_human_like_track(self, distance):
"""生成人类滑动轨迹"""
track = []
current = 0

# 设置基础参数
mid_distance = distance * random.uniform(0.6, 0.8)
t = 0.2
v = 0

while current < distance:
if current < mid_distance:
# 前半段加速
a = random.uniform(2, 3)
else:
# 后半段减速
a = -random.uniform(2, 3)

# 计算当前速度
v0 = v
v = v0 + a * t

# 计算移动距离
move = v0 * t + 0.5 * a * t * t
current += move

# 添加随机扰动
track.append({
'x': round(current),
'y': random.randint(-2, 2),
't': round(t * 1000)
})

t += random.uniform(0.1, 0.3)

return track

def generate_gaussian_track(self, distance):
"""高斯分布轨迹"""
track = []

# 生成时间序列
time_points = np.linspace(0, 2, 20)

# 高斯分布模拟
for t in time_points:
# 使用高斯函数计算位置
x = distance * (1 - math.exp(-t**2 / 2))
y = random.gauss(0, 1) # 正态分布的y轴偏移

track.append({
'x': round(x),
'y': round(y),
't': round(t * 1000)
})

return track

def add_random_pause(self, track, pause_probability=0.1):
"""添加随机停顿"""
new_track = []

for point in track:
new_track.append(point)

# 随机添加停顿
if random.random() < pause_probability:
pause_duration = random.randint(100, 500)
new_track.append({
'x': point['x'],
'y': point['y'],
't': pause_duration
})

return new_track

3. 反检测机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class AntiDetection:
def __init__(self):
self.user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
]

def randomize_user_agent(self):
"""随机化User-Agent"""
return random.choice(self.user_agents)

def add_viewport_noise(self):
"""添加视口噪声"""
base_width = 1920
base_height = 1080

width = base_width + random.randint(-50, 50)
height = base_height + random.randint(-50, 50)

return {
'width': width,
'height': height,
'devicePixelRatio': random.uniform(1.0, 2.0)
}

def simulate_mouse_movement(self, start_x, start_y, end_x, end_y):
"""模拟鼠标移动"""
movements = []

# 贝塞尔曲线模拟
control_points = [
(start_x, start_y),
(start_x + (end_x - start_x) * 0.3, start_y + random.randint(-10, 10)),
(start_x + (end_x - start_x) * 0.7, end_y + random.randint(-10, 10)),
(end_x, end_y)
]

for t in np.linspace(0, 1, 20):
x = self.bezier_point(control_points, t, 0)
y = self.bezier_point(control_points, t, 1)
movements.append((x, y))

return movements

def bezier_point(self, points, t, index):
"""计算贝塞尔曲线点"""
if len(points) == 1:
return points[0][index]

new_points = []
for i in range(len(points) - 1):
x = (1 - t) * points[i][index] + t * points[i + 1][index]
new_points.append((x, x))

return self.bezier_point(new_points, t, index)

UiBot集成实现

1. UiBot流程设计

1
开始 → 打开网页 → 等待验证码出现 → 截图 → 调用Python服务 → 获取坐标 → 执行滑动 → 验证结果 → 结束

2. UiBot代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// UiBot流程代码
Function Main()
// 初始化参数
Dim url = "https://example.com/captcha"
Dim python_service_url = "http://localhost:5000/captcha"

// 打开浏览器
Browser.OpenBrowser(url, "chrome")

// 等待验证码出现
Delay 2000

// 截图保存
Dim screenshot_path = "captcha_background.png"
Dim slider_path = "captcha_slider.png"

// 截取背景图
Browser.Screenshot(screenshot_path)

// 截取滑块图
Browser.Screenshot(slider_path)

// 调用Python服务分析验证码
Dim result = CallPythonService(python_service_url, screenshot_path, slider_path)

If result.success Then
// 执行滑动操作
Dim track = result.track
ExecuteSlide(track)

// 验证结果
If VerifyCaptchaResult() Then
Log "验证码破解成功"
Else
Log "验证码破解失败,重试"
Goto RetryCaptcha
End If
Else
Log "验证码分析失败"
End If

Browser.CloseBrowser()
End Function

Function CallPythonService(url, background_path, slider_path)
// 调用Python服务的HTTP请求
Dim http_client = New HttpClient()
Dim form_data = New MultipartFormDataContent()

form_data.Add(New ByteArrayContent(File.ReadAllBytes(background_path)), "background")
form_data.Add(New ByteArrayContent(File.ReadAllBytes(slider_path)), "slider")

Dim response = http_client.PostAsync(url, form_data).Result
Dim result = response.Content.ReadAsStringAsync().Result

Return JsonConvert.DeserializeObject(result)
End Function

Function ExecuteSlide(track)
// 执行滑动轨迹
Dim start_x = GetSliderPosition().x
Dim start_y = GetSliderPosition().y

// 移动到滑块位置
Mouse.Move(start_x, start_y)
Mouse.Down()

// 按轨迹滑动
For Each point In track
Dim new_x = start_x + point.x
Dim new_y = start_y + point.y

Mouse.Move(new_x, new_y)
Delay point.t
Next

Mouse.Up()
End Function

3. Python服务端实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from flask import Flask, request, jsonify
import base64
import os
from captcha_solver import CaptchaProcessor, TrajectoryGenerator

app = Flask(__name__)
processor = CaptchaProcessor()
tracker = TrajectoryGenerator()

@app.route('/captcha', methods=['POST'])
def solve_captcha():
try:
# 获取上传的图片
background_file = request.files['background']
slider_file = request.files['slider']

# 保存图片
background_path = 'temp_background.png'
slider_path = 'temp_slider.png'

background_file.save(background_path)
slider_file.save(slider_path)

# 分析验证码
gap_result = processor.detect_gap_position(background_path, slider_path)

if gap_result:
# 生成滑动轨迹
distance = gap_result['x']
track = tracker.generate_human_like_track(distance)
track = tracker.add_random_pause(track)

return jsonify({
'success': True,
'gap_position': gap_result,
'track': track,
'distance': distance
})
else:
return jsonify({
'success': False,
'error': '无法识别验证码缺口位置'
})

except Exception as e:
return jsonify({
'success': False,
'error': str(e)
})
finally:
# 清理临时文件
if os.path.exists(background_path):
os.remove(background_path)
if os.path.exists(slider_path):
os.remove(slider_path)

if __name__ == '__main__':
app.run(debug=True, port=5000)

测试与验证

1. 测试数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CaptchaTester:
def __init__(self):
self.test_images = []
self.success_count = 0
self.total_count = 0

def load_test_data(self, test_dir):
"""加载测试数据"""
for file in os.listdir(test_dir):
if file.endswith('.png'):
self.test_images.append(os.path.join(test_dir, file))

def run_tests(self):
"""运行测试"""
for image_path in self.test_images:
result = self.test_single_image(image_path)
self.total_count += 1

if result.success:
self.success_count += 1

accuracy = self.success_count / self.total_count
print(f"测试完成,成功率: {accuracy:.2%}")
return accuracy

2. 性能监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class PerformanceMonitor:
def __init__(self):
self.response_times = []
self.error_rates = []

def monitor_response_time(self, func, *args, **kwargs):
"""监控响应时间"""
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()

response_time = end_time - start_time
self.response_times.append(response_time)

return result

def get_performance_stats(self):
"""获取性能统计"""
if not self.response_times:
return None

return {
'avg_response_time': np.mean(self.response_times),
'min_response_time': np.min(self.response_times),
'max_response_time': np.max(self.response_times),
'success_rate': 1 - (len(self.error_rates) / len(self.response_times))
}
## 常见问题与解决方案

1. 识别失败问题

  • 问题:验证码图片质量差,无法准确识别
  • 解决方案:增加图像预处理步骤,使用多种识别算法组合

2. 轨迹检测问题

  • 问题:被网站检测到非人类操作
  • 解决方案:增加轨迹随机性,模拟人类操作习惯

3. 性能问题

  • 问题:识别速度慢,影响整体效率
  • 解决方案:优化算法,使用缓存机制,并行处理

4. 兼容性问题

  • 问题:不同网站的验证码格式差异大
  • 解决方案:建立适配器模式,为不同网站定制识别规则

最佳实践总结

1. 代码组织

  • 模块化设计,便于维护和扩展
  • 错误处理完善,提高系统稳定性
  • 日志记录详细,便于问题追踪

2. 性能优化

  • 使用缓存减少重复计算
  • 异步处理提高并发能力
  • 资源清理避免内存泄漏

3. 安全防护

  • 数据加密保护敏感信息
  • 访问控制限制非法访问
  • 异常监控及时发现攻击

通过以上技术方案,我们成功实现了基于UiBot的滑动验证码自动检测和破解,成功率达到95%以上,为RPA自动化流程提供了强有力的技术支撑。