295 lines
9.8 KiB
Markdown
295 lines
9.8 KiB
Markdown
# 滑块验证模块
|
||
|
||
本模块实现了豆瓣登录页面滑块验证码的自动检测和解决功能。
|
||
|
||
## 功能特性
|
||
|
||
- ✅ 自动检测滑块验证码中的缺口位置
|
||
- ✅ 支持多滑块检测(检测两个滑块并计算距离)
|
||
- ✅ 模拟人类滑动轨迹(贝塞尔曲线)
|
||
- ✅ 自动重试机制(最多 10 次)
|
||
- ✅ 滑块浮窗消失判定验证成功
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
src/slider/
|
||
├── cli.ts # 命令行工具,用于批量评估/标注
|
||
├── index.ts # 模块导出
|
||
├── types.ts # 类型定义
|
||
├── detector.ts # 主滑块检测器
|
||
├── detector-self-learning.ts # 自学习第二滑块检测
|
||
├── slider-controller.ts # 滑块移动控制器
|
||
├── validator.ts # 检测结果验证工具
|
||
├── detection/
|
||
│ └── candidate-search.ts # 候选区域搜索算法
|
||
└── utils/
|
||
├── geometry.ts # 几何计算工具
|
||
└── image.ts # 图像处理工具
|
||
```
|
||
|
||
## 运行输出约定
|
||
|
||
- 登录流程截取的**原始验证码**保存在项目根目录的 `noflag/`
|
||
- 自动检测产生的**标注结果**保存在根目录的 `output/`
|
||
- 可执行 `npm run slider -- --pic-dir=noflag` 对原始截图批量复核,结果同样输出至 `output/`
|
||
|
||
## 核心算法
|
||
|
||
### 1. 滑块检测 (`detector.ts`)
|
||
|
||
- **多策略候选搜索**:暗区域检测、边缘检测、颜色量化、LAB 色彩空间检测
|
||
- **候选框评分**:基于形状、色调一致性、内部边缘密度、梯度平滑度
|
||
- **边缘精炼**:使用 Sobel 边缘检测和投影分析精确定位滑块边界
|
||
|
||
### 2. 第二滑块检测 (`detector-self-learning.ts`)
|
||
|
||
- **模板匹配**:使用第一个检测到的滑块作为模板
|
||
- **边缘模板**:对图像和模板进行 Canny 边缘检测后匹配
|
||
- **位置验证**:确保第二个滑块在同一水平线上(y 轴偏差 < 25px)
|
||
|
||
### 3. 滑动控制 (`slider-controller.ts`)
|
||
|
||
- **距离计算**(v1.1.0 简化算法):
|
||
- **双滑块模式**:`距离 = (缺口X - 滑块X) / scaleX`
|
||
- 检测到左侧滑块(b1)和右侧缺口(b2)
|
||
- 计算两者左边界的水平距离
|
||
- 除以图像缩放比例(原始 340px → 检测用 800px)
|
||
- 原理:类比"两只小鸟嘴尖的水平距离"
|
||
- **单滑块模式**:`距离 = 缺口中心X / scaleX`
|
||
- 仅检测到缺口位置时的兜底方案
|
||
- 从起始位置直接滑动到缺口中心
|
||
- **图像缩放优化**:
|
||
- 原始验证码宽度:340px
|
||
- 放大到 800px 进行检测(scaleX ≈ 2.35)
|
||
- 提高小尺寸滑块的检测精度
|
||
- **拟人化滑动**:
|
||
- 使用 Playwright 的 `steps` 参数
|
||
- 平滑移动轨迹,避免机器人特征
|
||
|
||
## 使用方法
|
||
|
||
### 1. 环境变量配置
|
||
|
||
```bash
|
||
# 启用自动滑块验证
|
||
export DOUBAN_AUTO_SLIDER=1
|
||
|
||
# 设置手机号
|
||
export DOUBAN_PHONE=13800138000
|
||
|
||
# 运行登录脚本
|
||
npm run login
|
||
```
|
||
|
||
### 2. 编程接口
|
||
|
||
```typescript
|
||
import { SliderController } from './slider';
|
||
import { Page } from 'playwright';
|
||
|
||
const controller = new SliderController(10); // 最多尝试 10 次
|
||
|
||
const result = await controller.solveSlider(
|
||
page,
|
||
'.tcaptcha_drag_button', // 滑块按钮选择器
|
||
'#tcaptcha_iframe' // 验证码容器选择器
|
||
);
|
||
|
||
if (result.success) {
|
||
console.log(`验证成功!尝试 ${result.attempts} 次`);
|
||
} else {
|
||
console.log('验证失败');
|
||
}
|
||
```
|
||
|
||
### 3. 独立使用滑块检测器
|
||
|
||
```typescript
|
||
import { SliderDetector } from './slider';
|
||
|
||
const detector = new SliderDetector();
|
||
const boxes = await detector.detectSlider(
|
||
'captcha.png',
|
||
'output/captcha-annotated.png',
|
||
true
|
||
);
|
||
|
||
if (boxes && boxes.length > 0) {
|
||
console.log('检测到滑块:', boxes);
|
||
}
|
||
```
|
||
|
||
### 4. CLI 工具
|
||
|
||
```bash
|
||
npm run slider -- --pic-dir=images/douban
|
||
```
|
||
|
||
- 默认读取 `images/douban` 下的验证码图片并输出标注结果到 `images/output`
|
||
- 若存在 `ground-truth.json`,会自动评估检测精度和召回率
|
||
- 通过 `--pic-dir=子目录` 可切换其他图片集合
|
||
|
||
## 工作流程
|
||
|
||
1. **等待滑块出现**:检测页面中是否存在滑块验证码 iframe
|
||
2. **截图**:捕获验证码区域图像,保存原始图到 `noflag/` 目录
|
||
3. **图像预处理**:将图像缩放到 800px 宽度以提高检测精度
|
||
4. **多策略检测**:并行运行四种算法检测滑块候选框
|
||
- 暗区域检测(基于亮度阈值)
|
||
- Canny 边缘检测
|
||
- 颜色量化(K-means 聚类)
|
||
- LAB 色彩空间分析
|
||
5. **候选框评分与筛选**:
|
||
- 计算每个候选框的综合分数(形状、颜色、边缘)
|
||
- IoU 去重,合并重叠候选框
|
||
- 选择得分最高的两个滑块
|
||
6. **距离计算**:
|
||
- 双滑块:`(b2.x - b1.x) / scaleX`
|
||
- 单滑块:`b.x / scaleX`
|
||
7. **可视化标注**:在检测图上绘制红色框,保存到 `output/` 目录
|
||
8. **模拟滑动**:拖动左侧滑块到计算出的距离
|
||
9. **验证结果**:检查是否出现 `.tc-success` 成功标识
|
||
10. **失败重试**:点击刷新按钮,重新截图检测(最多 10 次)
|
||
|
||
## 参数说明
|
||
|
||
### SliderController 构造函数
|
||
|
||
```typescript
|
||
new SliderController(maxAttempts: number = 10)
|
||
```
|
||
|
||
- `maxAttempts`: 最大尝试次数,默认 10 次
|
||
|
||
### solveSlider 方法
|
||
|
||
```typescript
|
||
async solveSlider(
|
||
page: Page,
|
||
sliderSelector: string = '.tcaptcha_drag_button',
|
||
captchaSelector: string = '#tcaptcha_iframe'
|
||
): Promise<SliderSolveResult>
|
||
```
|
||
|
||
- `page`: Playwright 页面对象
|
||
- `sliderSelector`: 滑块按钮的 CSS 选择器
|
||
- `captchaSelector`: 验证码容器的 CSS 选择器
|
||
|
||
### 返回值 SliderSolveResult
|
||
|
||
```typescript
|
||
interface SliderSolveResult {
|
||
success: boolean; // 是否成功
|
||
attempts: number; // 尝试次数
|
||
distance?: number; // 滑动距离(像素)
|
||
}
|
||
```
|
||
|
||
## 依赖项
|
||
|
||
- `sharp`: 图像处理库,用于边缘检测、颜色量化等
|
||
- `playwright`: 浏览器自动化,用于截图和鼠标操作
|
||
|
||
## 注意事项
|
||
|
||
1. **选择器适配**:不同网站的滑块选择器可能不同,需要根据实际情况调整
|
||
2. **截图位置**:临时截图保存在 `os.tmpdir()/douban-slider/` 目录
|
||
3. **成功判定**:通过检查验证码浮窗是否消失来判断验证是否成功
|
||
4. **失败处理**:自动验证失败后会提示用户手动完成
|
||
|
||
## 调试
|
||
|
||
如需查看检测过程中的日志,观察控制台输出:
|
||
|
||
```
|
||
[SliderController] 开始滑块验证,最多尝试 10 次
|
||
[SliderController] 等待验证码 iframe 加载...
|
||
[SliderController] 验证码 iframe 已加载
|
||
[SliderController] 等待滑块背景图加载...
|
||
[SliderController] 滑块背景图已加载
|
||
[SliderController] ===== 第 1/10 次尝试 =====
|
||
[SliderController] 已截图到: /Users/gavin/douban-login/noflag/captcha-20250125-123456.png
|
||
[SliderDetector] 图像已缩放: 340x191 -> 800x449 (scaleX=2.35)
|
||
[SliderDetector] 检测到 2 个滑块候选框
|
||
[SliderDetector] 滑块 1: x=45, width=60, score=0.85
|
||
[SliderDetector] 滑块 2: x=195, width=55, score=0.82
|
||
[SliderDetector] 已保存标注图: /Users/gavin/douban-login/output/captcha-20250125-123456-detected.png
|
||
[SliderController] ✓ 检测到 2 个滑块
|
||
[SliderController] 计算距离: (195 - 45) / 2.35 = 63.8px
|
||
[SliderController] 开始拖动滑块 64px
|
||
[SliderController] ✓ 滑块验证成功!(1000ms后窗口消失)
|
||
[SliderController] 验证成功!共尝试 1 次
|
||
```
|
||
|
||
**关键日志说明**:
|
||
- `图像已缩放`: 显示原始尺寸、检测尺寸和缩放比例
|
||
- `检测到 N 个滑块候选框`: N=2 表示双滑块模式,N=1 表示单滑块模式
|
||
- `滑块 1/2`: 显示每个滑块的 x 坐标、宽度和评分
|
||
- `已保存标注图`: 红框标注结果的保存路径
|
||
- `计算距离`: 显示详细的距离计算公式
|
||
- `✓ 滑块验证成功`: 检测到腾讯验证码的成功标识
|
||
|
||
## 故障排查
|
||
|
||
### 1. 检测不到滑块
|
||
|
||
**症状**:日志显示"未检测到滑块"
|
||
|
||
**排查步骤**:
|
||
- 检查 `noflag/` 目录下的原始截图是否正确
|
||
- 确认验证码已完全加载(等待 iframe 和图片元素)
|
||
- 查看 `output/` 目录的标注图,确认候选框是否被正确识别
|
||
- 调整 `candidate-search.ts` 中的检测阈值
|
||
|
||
### 2. 滑动距离不准确
|
||
|
||
**症状**:滑块滑过头或不够远
|
||
|
||
**排查步骤**:
|
||
- 查看日志中的 `scaleX` 值(应该约为 2.35)
|
||
- 确认使用的是双滑块模式还是单滑块模式
|
||
- 检查 `output/` 目录标注图,红框是否准确框住滑块
|
||
- 验证距离计算公式:`(b2.x - b1.x) / scaleX`
|
||
|
||
**v1.1.0 改进**:
|
||
- 简化了距离计算逻辑,移除复杂的坐标转换
|
||
- 采用"两只小鸟距离"原理,直接计算左边界差值
|
||
|
||
### 3. 验证总是失败
|
||
|
||
**症状**:滑动后没有出现成功提示
|
||
|
||
**可能原因**:
|
||
- 滑动距离计算错误(参见上一条)
|
||
- 触发反爬虫检测(轨迹太机械)
|
||
- 网络延迟导致成功标识未及时显示
|
||
|
||
**解决方案**:
|
||
- 检查日志中的滑动距离是否合理(通常 50-150px)
|
||
- 增加成功判定的等待时间(当前 1000ms)
|
||
- 尝试多次重试(当前最多 10 次)
|
||
- 查看浏览器开发者工具,确认 `.tc-success` 类名是否出现
|
||
|
||
### 4. 视觉调试技巧
|
||
|
||
**查看检测结果**:
|
||
1. 运行登录后,打开 `output/` 目录
|
||
2. 找到最新的 `*-detected.png` 文件
|
||
3. 检查红框是否准确标注了滑块和缺口
|
||
4. 对比 `noflag/` 目录的原始图,确认缩放和标注的准确性
|
||
|
||
**理想的标注结果**:
|
||
- 左侧滑块:红框紧贴滑块边缘
|
||
- 右侧缺口:红框框住缺口区域
|
||
- 两个红框高度基本一致(y 坐标偏差 < 25px)
|
||
|
||
## 移植说明
|
||
|
||
本模块从 `captcha_cracker` 项目移植而来,并进行了以下扩展:
|
||
|
||
1. 原样保留检测、标注、CLI 与验证器等核心能力
|
||
2. 新增 Playwright 集成,用于自动截图和滑块拖动
|
||
3. 添加登录流程的滑块控制器与重试机制
|
||
4. 调整脚本入口与文档,便于在豆瓣登录场景复用
|