# 滑块验证模块 本模块实现了豆瓣登录页面滑块验证码的自动检测和解决功能。 ## 功能特性 - ✅ 自动检测滑块验证码中的缺口位置 - ✅ 支持多滑块检测(检测两个滑块并计算距离) - ✅ 模拟人类滑动轨迹(贝塞尔曲线) - ✅ 自动重试机制(最多 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 ``` - `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. 调整脚本入口与文档,便于在豆瓣登录场景复用