ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

滑块验证码识别与模拟:AI图像识别+轨迹算法全解析

滑块验证码识别与模拟:AI图像识别+轨迹算法全解析 简介滑块验证码是网站反自动化的重要防线其校验已从简单前端拖拽升级为融合行为轨迹与环境检测的动态决策。理解其攻防逻辑需要从图像识别、自动化框架与轨迹算法等基础技术入手。本文从OpenCV边缘检测定位缺口到ddddocr深度学习识别兜底再到利用贝塞尔曲线模拟人工拖动节奏并结合Playwright执行浏览器操作系统拆解了验证码识别与模拟的关键链路。相关技术可服务于接口自动化测试、合规数据采集等场景为工程实践提供可靠的参考方案。 滑块验证码这东西做爬虫和自动化测试的朋友应该都不陌生。早期那批纯前端校验的滑块基本是摆设拖过去就完事。后来各家厂商学聪明了把校验逻辑搬到了后端加上了行为轨迹分析、设备指纹、环境检测普通的“快拖快放”立刻失效。这个项目的核心就是围绕“AI图像识别”和“滑块验证码”之间的攻防关系拆解一套相对完整的自动化识别与模拟拖动方案。我整理这份内容不是鼓励你去攻击别人的业务系统而是站在技术研究、接口自动化测试、合规数据采集的角度看看滑块验证码的对抗逻辑到底是怎么运转的。开头先说结论一个能稳定过滑块验证码的方案至少要解决三个问题。第一缺口位置怎么找这决定了你要往哪儿拖第二拖动轨迹怎么生成这决定了后端行为检测会不会把你判定成机器第三整个操作在哪里执行这决定了你是在跟浏览器环境对抗还是在跟纯接口对抗。这三个问题恰好对应了AI图像识别、轨迹算法和自动化框架三个技术栈。下面我按照实际项目推进的顺序把每一块的关键细节都拆开讲。1. 滑块验证码的形态与校验逻辑做任何对抗之前先摸清楚对手的结构。滑块验证码并不是大家想象中“一张图加一个滑块”那么简单它在不同厂商、不同版本里的实现差异非常大。1.1 主流滑块验证码的类型划分第一种是凹槽型滑块。极验、网易易盾早期版本比较常见背景图上有一个缺了一块拼图的凹槽拖到缺口位置就算通过。这种类型视觉特征明显识别难度相对低重点在于轨迹模拟。第二种是拼图型滑块。阿里云、腾讯防水墙的一些版本喜欢用这种背景图是一张完整图片加一个拼图碎片碎片形状和目标位置都随机生成需要把碎片拖到正确位置。这种类型的图片干扰噪点更多识别难度更高一点而且缺口位置往往带阴影对边缘检测不太友好。第三种是滑块内嵌字符或图案的类型。背景图上会有几个字符、图标或者物体要求用户按特定顺序连线或者滑动到对应位置。这种已经带有语义理解成分单纯靠OpenCV边缘检测往往不够需要目标检测模型介入。1.2 后端校验的三个关键维度后端到底在验什么把这个问题想明白整个项目思路就清晰了。滑块验证码的后端校验说白了就是三件事叠加。第一轨迹校验。从按下鼠标到松开鼠标期间产生的每个坐标点都会被记录后端会分析这些点的位移速度、加速度变化、停顿位置、抖动幅度。机器生成的匀速直线轨迹一眼就被识别但过于随机、毫无规律的轨迹同样可疑因为真人拖动滑块时肌肉控制会产生一种“从快到慢再微调”的节奏感。第二环境校验。浏览器指纹、WebDriver特征、User-Agent、Canvas指纹、WebGL信息、时区语言、网络代理的匿名度这些东西会被打包提交到服务端。比如Selenium和Pillow生成的图片经过浏览器处理后Canvas指纹会暴露出自动化环境的痕迹。第三整体决策。服务端会把轨迹数据、环境数据、图片操作行为合并到一个风险评估模型里。有的厂商还加入了“滑动耗时是否合理”这类简单的启发式规则比如从按下到松开的时长少于200毫秒或超过10秒基本都会直接判定失败。理解了这些也就明白为什么网上很多“打码平台”能稳定的原因——它们不是只靠一张识别出来的坐标而是把轨迹、环境、代理质量整套逻辑都做了工程化处理。这也是我建议你在自己研究时不要只盯着“识别”这一步的原因。2. AI图像识别定位缺口的核心思路回到这个项目最核心的部分滑块缺口识别。这一步做得好不好直接决定整体成功率。目前业内常见做法有两种路线我分别展开讲。2.1 为什么边缘检测优先于目标检测对大多数凹槽型和拼图型滑块第一步尝试的都应该是边缘检测而不是一上来就上深度学习。原因是成本与确定性。边缘检测是像素级别的计算速度快、可控性强、结果稳定而目标检测模型需要准备标注数据集、训练、调参而且输出的边界框还有一定的回归误差。实际测试下来在干净的背景图上Canny算子配连通域分析的识别精度已经能到95%以上完全没有必要用大炮打蚊子。边缘检测的基本流程是这样的先读取背景图和滑块图转成灰度图然后用Canny算子提取边缘接着用轮廓查找找到滑块区域的轮廓最后计算轮廓的中心坐标。关键在于Canny算子的阈值参数这个值需要根据具体的背景图微调不能用固定值。import cv2 import numpy as np def find_gap(bg_path, gap_path): bg cv2.imread(bg_path, cv2.IMREAD_GRAYSCALE) gap cv2.imread(gap_path, cv2.IMREAD_GRAYSCALE) # 边缘检测 bg_edge cv2.Canny(bg, 100, 200) gap_edge cv2.Canny(gap, 100, 200) # 模板匹配定位滑块在背景图中的位置 result cv2.matchTemplate(bg_edge, gap_edge, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(result) # 返回滑块左上角位置 h, w gap_edge.shape return max_loc[0] w / 2, max_loc[1] h / 2上面这种方式是“模板匹配边缘检测”的组合适合背景图和滑块图两者都相对标准的情况。但实际场景里背景图上往往有大量干扰纹理滑块本身也可能带彩色边框和阴影直接模板匹配的效果会打折扣。这时候就需要先做一次高斯模糊去除噪声再尝试adaptiveThreshold自适应阈值分割最后找轮廓。经验之谈如果背景图的纹理特别乱可以先把图像缩放到一个固定宽度比如保持原图宽度的0.5倍识别出坐标后再乘以缩放比例还原。这样能降低很多计算量同时能过滤掉小尺度噪声。2.2 深度学习方法与 ddddocr 的差异化定位当边缘检测搞不定时就该上深度学习了。这个项目标题里出现了 ddddocr我多说两句。ddddocr 是一个开源的OCR工具库早期主要是用来识别图文验证码的后来加入了滑块验证码的识别能力。新版本里它提供了专门的缺口识别接口使用起来非常方便import ddddocr ocr ddddocr.DdddOcr(detFalse, ocrFalse, show_adFalse) with open(bg.png, rb) as f: bg_bytes f.read() with open(target.png, rb) as f: target_bytes f.read() res ocr.slide_match(bg_bytes, target_bytes, simple_targetTrue) print(res[target])ddddocr 走的是深度学习路线内部使用了CNN之类的模型对缺口轮廓和阴影的鲁棒性比传统图像处理好很多。但这也带来了一个问题它是一个通用模型如果遇到厂商做了特殊的图片预处理比如加了大面积干扰线、或者把缺口做成不规则形状它的精度也会下降。我实际测试下来的结论是边缘检测适合干净的背景图ddddocr 适合带纹理和噪点的图两者互相补充不能互相替代。工程上最合理的做法是先跑一遍边缘检测拿结果跟模板大小比对一下如果置信度低再切换到 ddddocr用两个结果做交叉验证。这个策略成功率要比单用任一方案高出一截。2.3 坐标换算必须考虑前端缩放这块特别容易踩坑但很多教程都不提。你在后端识别出来的坐标是图片像素坐标而浏览器里实际执行拖动的坐标是页面上的CSS像素坐标。两者之间存在一个缩放比例尤其是在高DPI屏幕上这个比例不等于1。function getScale() { const img document.querySelector(.slider-bg); const rect img.getBoundingClientRect(); return rect.width / img.naturalWidth; }还有个更隐蔽的问题有的滑块验证码组件会在背景图上人为加一条白色或透明的通道让图片向右偏移一部分从而提高识别难度。遇到这种情况直接用目标坐标去拖永远差那么几十个像素你怎么试都过不了。解决办法是手动观察这种偏移量在计算最终拖动距离时把偏移值减掉。3. 拟人化拖动轨迹的实现识别出缺口坐标只是第一步真正决定成败的其实是轨迹。我可以直接说轨迹算法决定你能过多少家验证码。很多初学者的代码能识别出缺口但拖动那一下实在太过机械后端一检测就露馅。3.1 轨迹为什么会被识别出来人类拖动滑块的过程不是一个匀速运动。手指按下鼠标的那一瞬间有一个从静止到加速的启动过程移动到目标位置附近时速度会逐渐放缓到达目标点之后还有一个小的回弹或停顿。整个过程的时间在几百毫秒到一两秒之间而且伴随着轻微的、无规律的抖动。机器生成轨迹最常见的缺陷是所有坐标点的速度都收敛在一条光滑曲线上。比如用线性插值生成的轨迹位移对时间完全线性速度恒定用简单的二次函数模拟加速又显得过于规整。后端的大数据模型早就把这类轨迹特征学得明明白白一眼就看穿。3.2 基于贝塞尔曲线与分段函数的轨迹模拟比较务实的轨迹生成方案是分段函数模拟第一段非匀加速启动第二段接近匀速或轻微波动第三段减速靠近目标第四段修正微调。以三次贝塞尔曲线为例生成逻辑是取起点到终点之间的几个控制点让曲线自然弯曲然后按时间戳采样坐标。这样做出来的轨迹视觉上像人手画出来的速度曲线也更自然。import numpy as np def bezier_curve(p0, p1, p2, p3, n100): t np.linspace(0, 1, n) x (1 - t)**3 * p0[0] 3 * (1 - t)**2 * t * p1[0] 3 * (1 - t) * t**2 * p2[0] t**3 * p3[0] y (1 - t)**3 * p0[1] 3 * (1 - t)**2 * t * p1[1] 3 * (1 - t) * t**2 * p2[1] t**3 * p3[1] return list(zip(x, y))但纯贝塞尔还有一个问题——它太光滑了真人轨迹其实带有微观抖动。所以更高级的写法是在贝塞尔曲线基础上叠加一个随机扰动量扰动的方差随速度变化速度快的时候抖动小速度慢下来时抖动明显一点。这种“噪声叠加”技法才是拟人化轨迹的核心。极验在v3/v4版本里的轨迹检测基本是基于“位移-时间”序列做的机器学习分类。我见过不少通过率稳定的方案都是用分段三次样条插值配合随机数生成不规则的停顿时间同时保证轨迹不越过目标点太多。3.3 拖动操作的技术落地轨迹算法写好了还需要一个执行环境。目前主流的自动化方案主要有三个Selenium WebDriver、Playwright、以及纯接口模拟。Selenium 最大的优点是生态成熟、资料多缺点是特征过于明显WebDriver属性在高级验验证码面前会被直接识别。Playwright 做了很多反检测优化比如隐藏自动化痕迹实际效果比Selenium好很多。纯接口模拟是最难的需要把图片下载、缺口识别、轨迹计算全部提到本地完成然后直接调用验证码校验接口提交轨迹数据。这种方式在应对不依赖浏览器的纯接口校验时非常高效但对协议逆向的要求极高。以 Playwright 为例执行拖动的标准做法是使用 page.mouse 系列方法模拟鼠标按下、移动、抬起并且要配合 page.wait_for_timeout 制造随机间隔from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://example.com) slider page.locator(.slider-btn) box slider.bounding_box() start_x box[x] box[width] / 2 start_y box[y] box[height] / 2 page.mouse.move(start_x, start_y) page.mouse.down() for x, y in trajectory: page.mouse.move(start_x x, start_y y, steps1) page.wait_for_timeout(random.randint(10, 30)) page.mouse.up()注意mouse.move 的 steps 参数要调到最小不然它会强制做插值把你精心设计的轨迹给“平滑”掉。这一步是很多人的代码明明轨迹生成得很漂亮但拖出去却非常离谱的原因。4. 实操从零搭建一个滑块识别链路理论讲完下面走一遍实操。这里我以“目标站点使用极验滑块验证码”为例从环境准备到最终跑通把每个环节的命令、代码和参数选择都摆出来。4.1 环境准备与工具选型我建议在3.10的Python版本上操作因为很多依赖库已经停止维护老版本。需要安装的依赖如下opencv-python图像处理Canny边缘检测、轮廓查找、模板匹配。ddddocr深度学习缺口识别作为备选方案。playwright浏览器自动化支持chromium/firefox/webkit。numpy轨迹生成和数值计算。requests用于下载验证码图片和模拟接口请求。pip install opencv-python ddddocr playwright numpy requests playwright install chromium选型这条路上我踩过的坑是不要一开始就上无头模式。调试阶段一定用 headed 模式把浏览器窗口打开亲眼看一看识别结果和拖动过程定位问题是出在识别、轨迹还是环境检测上。无头模式跑通了再切 headless能节省大量排查时间。4.2 缺口识别模块实现这个模块的逻辑分两层先快速跑一遍传统图像处理结果置信度不够再调深度学习。传统方式可以先用Canny提取背景图的边缘然后做轮廓检测通过轮廓面积过滤小块噪声最后通过轮廓外接矩形的宽高比筛选出疑似缺口。def find_target_contour(bg_img): gray cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY) blurred cv2.GaussianBlur(gray, (5, 5), 0) edged cv2.Canny(blurred, 60, 160) contours, _ cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) best None for c in contours: x, y, w, h cv2.boundingRect(c) if w 20 or h 20: continue area w * h if best is None or area best[0]: best (area, x, y, w, h) return best如果这个函数找不到像样的目标就切换 ddddocr 的 slide_match 接口做兜底。两者交叉验证后再根据前端缩放比例算出最终拖动距离。这里有一个很容易被忽略的细节要把背景图原始尺寸传给识别函数同时记录浏览器里这张图实际渲染的尺寸。因为很多前端框架会把背景图和滑块图放在一个固定大小的容器里CSS尺寸和图片原始尺寸不一致。我见过一个项目所有识别坐标都对但就是差200多个像素后来发现是浏览器页面缩放导致 CSS 坐标和图片坐标不一致。4.3 轨迹生成与拖动执行轨迹模块我习惯拆成两个类一个负责生成轨迹点一个负责执行拖动。轨迹生成类里除了贝塞尔函数还会加上一个微调逻辑以目标点为终点但在最后5个采样点里加入不超过3像素的随机偏移模拟真人松手前的那股“不太自信”的微调感。def generate_track(distance): track [] current 0 mid distance * 0.7 # 第一阶段加速 while current mid: step random.randint(3, 8) current step track.append(current) # 第二阶段减速微调 while current distance: step random.randint(1, 3) current step track.append(current) return track执行部分用 Playwright 的 mouse 事件配合 random 库控制每个采样点之间的等待时间。重点是把总时长控制在600到1500毫秒之间随机分布。4.4 完整流程串联与细节处理串联的时候我在代码里加了一个“失败重试”逻辑。如果第一次拖动后没有通过验证先分析失败原因是滑块没到缺口就松手了还是到缺口后没触发成功回调还是被检测到轨迹异常。针对不同情况重新生成轨迹或重新识别缺口。实际工程里识别模块的返回结果不一定是单一整数我应该同时返回置信度、识别出的外接矩形宽高和中心点。这样上层判断逻辑可以根据置信度决定要不要进行二次识别也可以根据矩形宽高验证识别结果是不是一个合理的滑块宽度。5. 常见问题与排查技巧实录这部分内容是我跑完无数个验证码之后汇总的实战经验价值甚至比前面的核心代码更高。真实环境里的那些坑文档里是找不到的。5.1 典型问题速查表问题现象可能原因解决方案缺口识别偏移 10-30 像素前端缩放比例未考虑用 getBoundingClientRect 比对图片原始尺寸拖动后提示“环境异常”浏览器的 webdriver 特征未被隐藏使用 Playwright 的 chromium.launch 而非 Selenium或开启 stealth 插件拖动到目标点但提示失败轨迹过于平滑增加随机抖动与停顿不要用纯线性插值识别结果反复跳动背景图存在多个相似纹理区域用多个阈值参数跑 Canny取面积最大的区域或切换深度学习模型请求验证码接口被拒绝代理IP被标记检查代理质量改用家庭住宅IP无头模式下行为异常无头模式缺少部分浏览器特征加 --disable-blink-featuresAutomationControlled 参数5.2 几个必须单独说的坑第一个坑是 WebDriver 检测。Selenium 默认会在 window.navigator 上挂一个 webdriver 属性验证码的JS能直接读到。Playwright 默认没有这个问题但也不是完全不可查。你可以用 CDPChrome DevTools Protocol手动注入一段初始化脚本把 navigator.webdriver 改回 undefined同时修正 navigator.plugins 和 navigator.languages让浏览器环境看起来更像普通用户。第二个坑是滑块按钮的初始位置。极验的滑块按钮不是从图片最左边开始的它在页面布局里有自己的边距。直接把识别坐标当成拖动距离会偏得离谱。正确做法是先用 bounding_box 拿到按钮的初始X坐标再用目标缺口的X坐标减去按钮初始X坐标得到的差值才是真正要拖动的距离。第三个坑是时间戳和轨迹点数量的关系。后端分析轨迹的时候会把每个轨迹点之间的时间间隔一起计算。如果你的轨迹点有100个但总时间只有400毫秒每个点的平均间隔只有4毫秒这不符合人手的物理极限。解决方法是根据轨迹点数量动态调整总时长保证平均间隔在15到30毫秒之间。第四个坑是关于“复现率”的。很多刚入门的同学跑通一次就以为是成功实际上滑块验证码是概率性通过的系统必须做多次测试统计通过率。同一个环境、同一套代码极验的通过率能做到70%以上就算不错。通过率低于50%时优先考虑是不是轨迹特征过于统一而不是识别不够准。5.3 合规边界与个人建议最后说一句大实话。滑块验证码本质上是为了保护网站业务不被自动化攻击你去研究它、理解它对做安全测试和自动化测试有正面价值。但如果把这套技术用于批量注册、秒杀抢购、刷量刷单这类场景既违反平台规则也可能触碰法律红线。我自己的定位是在研究这个项目时所有测试目标都是自己搭建的测试环境或已经获得授权的目标站点。关于这一点没有妥协空间。如果你是想靠它走灰产路子那我这篇文章不适合你。回到技术本身滑块验证码的识别与模拟是一个非常典型的“工程问题”图像识别解决空间定位轨迹算法解决时间序列伪造自动化框架解决执行环境。三块技术栈各管一段单独拿出来都不难难在把它们无缝衔接成一个稳定系统。做多了之后你会发现这套思路其实也可以迁移到很多其他场景比如手势密码破解、滑动拼图验证、甚至一些智能操作系统的UI自动化测试底层的“感知-决策-执行”架构是完全相通的。希望这篇文章能给你一些实际的启发少走一点我当年走过的弯路。本文还有配套的精品资源点击获取
返回列表