资讯详情 零基础油猴脚本开发:自动处理算术验证码的完整实践
📅 2026/10/21 6:53:55
1. 从零上手油猴脚本一个自动处理算术验证码的完整实践很多人第一次听到“油猴脚本”这四个字脑子里浮现的可能是某种黑客工具或者觉得这是前端工程师才玩得转的东西。其实完全不是。油猴脚本本质上就是一段运行在浏览器里的 JavaScript它能在你打开某个网页的时候自动执行一些操作——比如自动填表单、自动点击按钮、自动提取页面数据甚至自动帮你算一道算术题然后把答案填进去。我当初接触它就是因为每天要在一个内部系统里反复登录每次都要手动算一道“3加7等于几”这种验证码烦得不行。后来花了一个周末从零开始学写出了一个能自动识别并填写算术验证码的脚本从此再也没手动算过。这篇文章就是把我那两天的学习路径、踩过的坑、以及最终跑通的完整方案原原本本地拆开讲一遍。不管你是完全没写过代码的小白还是有点编程基础但没碰过浏览器脚本的开发者都能跟着走一遍。核心关键词就三个油猴脚本开发、算术验证码自动处理、零基础自学。我会从最基础的环境搭建讲起一直讲到脚本的完整实现和调试技巧中间所有涉及到的原理、参数、操作步骤都会给出详细的解释和可直接复制的代码。先说清楚这个项目能做什么它能在你打开目标网页时自动找到页面上的算术验证码图片或文字解析出题目内容计算出答案然后把答案填入对应的输入框。整个过程不需要你手动干预。适合谁来学适合任何需要频繁登录某个系统、又被算术验证码折磨的人也适合想学一点浏览器脚本开发但不知道从什么项目入手的新手。因为这个项目足够小、足够完整而且效果立竿见影。2. 油猴脚本到底是什么核心概念与运行机制拆解2.1 油猴脚本的本质浏览器里的“自动化小助手”油猴脚本的正式名称叫“用户脚本”User Script它是一段 JavaScript 代码通过浏览器扩展最常见的是 Tampermonkey 和 Violentmonkey注入到指定网页中运行。你可以把它理解成一个“网页改造器”——网页原本长什么样、有什么功能是网站开发者决定的但油猴脚本可以在网页加载完成后往里面塞东西、改东西、点东西。比如网页上有一个你每天都要点的按钮你可以写个脚本让它自动点网页上有一个验证码你可以写个脚本让它自动填。它的运行机制是这样的浏览器扩展负责管理你写的脚本根据脚本头部声明的匹配规则比如match或include决定在哪些网页上执行这段代码。当你在浏览器里打开一个匹配的网页时扩展会在页面加载的某个时机由run-at控制把脚本注入进去。脚本一旦注入就拥有了和网页本身 JavaScript 一样的权限——能读取页面元素、修改页面内容、发起网络请求、监听事件等等。这里有一个关键点需要理解油猴脚本运行在网页的上下文里但它并不是网页的一部分。它更像是你请了一个“外挂助手”站在网页旁边帮你操作网页。这个助手能看到网页上的一切也能对网页做任何事但网页本身并不知道这个助手的存在除非脚本主动暴露自己。2.2 为什么选油猴脚本而不是其他方案处理算术验证码这件事理论上有很多种做法。你可以用 Python 写个自动化脚本配合 Selenium 来控制浏览器也可以用浏览器自带的开发者工具手动改页面还可以用一些商业化的自动化工具。但这些方案各有各的问题。Python Selenium 的方案确实强大但它的重量级太高了。你需要安装 Python 环境、安装 Selenium 库、下载对应的浏览器驱动、写一套完整的控制逻辑。而且它启动的是一个全新的浏览器实例和你日常使用的浏览器是分开的——你的登录状态、Cookie、书签都不在里面。对于“每天登录某个系统”这种场景来说这太重了。浏览器开发者工具手动改页面每次都要打开控制台、找到元素、手动输入代码这跟手动填验证码没什么区别完全没有自动化。油猴脚本的优势在于它直接运行在你日常使用的浏览器里你的登录状态、Cookie、会话都是现成的。你只需要写一次脚本之后每次打开目标网页它自动执行。轻量、无感、零额外环境依赖。对于“自动处理算术验证码”这个具体需求来说油猴脚本是最合适的方案。2.3 算术验证码的常见形式与技术难点算术验证码看起来简单但实际处理时会遇到几种不同的形式每种的技术难度不一样。第一种是纯文字形式的算术题比如页面上直接显示“3 7 ?”这种最好处理直接用 JavaScript 读取页面上的文字内容用正则表达式提取出数字和运算符计算出结果就行。第二种是图片形式的算术题题目被渲染成一张图片比如“5 2 ?”被画成一张 PNG 图片显示在页面上。这种就麻烦一些因为 JavaScript 本身没法直接“看懂”图片内容。你需要借助 OCR光学字符识别技术或者针对特定网站的验证码图片做模板匹配。第三种是 Canvas 绘制的算术题题目通过 HTML5 的 Canvas 元素动态绘制出来。这种比图片形式更灵活因为 Canvas 的绘制逻辑在 JavaScript 里是可追踪的有时候可以直接拦截绘制指令来获取题目内容。还有一种比较少见的是通过 CSS 背景图或者 SVG 来展示题目处理思路和图片形式类似。我这个项目主要针对第一种和第二种形式因为这两种在实际系统中最常见。第一种直接解析文字第二种用 OCR 或者模板匹配来处理。下面会分别讲具体的实现方法。3. 开发环境搭建从安装扩展到第一个脚本3.1 安装脚本管理器扩展油猴脚本本身只是一段代码它需要一个“宿主”来管理和执行。这个宿主就是脚本管理器扩展。目前主流的有两个Tampermonkey 和 Violentmonkey。两个都很好用Tampermonkey 用户量更大、文档更全Violentmonkey 开源、轻量。我个人习惯用 Tampermonkey下面的操作也以它为例。安装步骤很简单打开浏览器的扩展商店搜索“Tampermonkey”找到后点击安装。安装完成后浏览器工具栏上会出现一个图标点击图标能看到管理面板。如果你用的是某些不能直接访问扩展商店的浏览器也可以手动下载扩展文件进行安装但这里不展开讲因为操作路径比较绕。安装完成后建议做一件事在扩展的设置里把“配置模式”改成“高级”这样能看到更多的脚本管理选项比如脚本的更新策略、注入时机等。对于开发调试来说高级模式更方便。3.2 创建你的第一个脚本点击 Tampermonkey 图标选择“添加新脚本”会打开一个代码编辑器页面。默认会生成一段模板代码大概长这样// UserScript // name New Userscript // namespace http://tampermonkey.net/ // version 0.1 // description try to take over the world! // author You // match https://www.example.com/* // icon https://www.google.com/s2/favicons?sz64domainexample.com // grant none // /UserScript (function() { use strict; // Your code here... })();这段代码里最重要的部分是// UserScript和// /UserScript之间的头部注释。这些注释不是普通的注释而是脚本的“元数据”告诉 Tampermonkey 这个脚本叫什么名字、在哪些网页上运行、需要什么权限等等。几个关键的元数据字段需要解释一下name是脚本的名称随便起自己能认出来就行。match是最重要的字段它决定了脚本在哪些网页上执行。格式是协议://域名/路径支持通配符。比如https://www.example.com/*表示在 example.com 的所有页面上运行。如果你想在多个网站上运行可以写多行match。grant字段控制脚本能使用哪些特殊的 API。none表示不使用任何特殊 API脚本只能操作页面本身。如果你需要跨域请求、访问本地存储等需要设置对应的值比如GM_xmlhttpRequest、GM_setValue等。对于我们的算术验证码脚本来说如果 OCR 是在本地做的grant none就够了如果需要调用外部 OCR 接口就需要GM_xmlhttpRequest。run-at字段控制脚本的注入时机。默认是document-idle也就是页面加载完成后才执行。对于验证码处理来说这个时机通常够用因为验证码一般是在页面加载后才渲染出来的。但如果验证码是页面加载过程中动态生成的可能需要改成document-start或document-end更早地介入。3.3 理解脚本的注入时机与执行环境脚本的注入时机对验证码处理非常关键。如果你在页面还没加载完的时候就去找验证码元素肯定找不到。但如果你等太久用户可能已经手动填完了脚本再填也没意义。document-start是最早的时机此时页面的 DOM 还没开始构建。这个时机适合做“拦截”类操作比如拦截某个网络请求、修改某个全局变量。对于验证码处理来说这个时机太早了。document-end是 DOM 构建完成但外部资源图片、样式表可能还没加载完的时机。如果你的验证码是纯文字形式这个时机就可以开始处理了。但如果是图片形式需要等图片加载完才能识别这个时机可能还不够。document-idle是页面完全加载完成的时机包括所有外部资源。这是最安全的时机但也是最晚的。对于大多数验证码场景来说这个时机是合适的。我通常的做法是先用document-idle作为默认时机然后在脚本里加一个轮询机制每隔几百毫秒检查一次验证码元素是否出现。如果出现了就处理没出现就继续等。这样既不会太早也不会太晚。4. 算术验证码的识别与计算核心逻辑实现4.1 文字型验证码的解析方法文字型算术验证码是最简单的一种。页面上直接显示类似“3 7 ?”这样的文本你只需要用 JavaScript 读取这段文本然后用正则表达式提取出数字和运算符最后计算出结果。假设页面上验证码的 HTML 结构是这样的span idcaptcha-question3 7 ?/span input idcaptcha-answer typetext /那么脚本的核心逻辑就是const questionEl document.querySelector(#captcha-question); const answerInput document.querySelector(#captcha-answer); if (questionEl answerInput) { const questionText questionEl.textContent.trim(); // 用正则提取数字和运算符 const match questionText.match(/(\d)\s*([\-*×÷])\s*(\d)/); if (match) { const num1 parseInt(match[1], 10); const operator match[2]; const num2 parseInt(match[3], 10); let answer; switch (operator) { case : answer num1 num2; break; case -: answer num1 - num2; break; case *: case ×: answer num1 * num2; break; case ÷: answer Math.floor(num1 / num2); break; } answerInput.value answer; // 触发 input 事件让页面知道输入框的值变了 answerInput.dispatchEvent(new Event(input, { bubbles: true })); } }这段代码看起来简单但有几个细节需要注意。第一正则表达式要考虑到运算符可能有多种写法比如乘号可能是*也可能是×除号可能是/也可能是÷。第二除法要注意整除问题有些验证码要求结果是整数所以用Math.floor取整。第三填入答案后要手动触发input事件因为很多现代网页框架比如 React、Vue不会自动检测到通过 JavaScript 直接修改value的变化需要手动派发事件来通知页面。4.2 图片型验证码的 OCR 处理思路图片型验证码就麻烦一些了。页面上显示的是一张图片比如img src/captcha.png /你需要先获取这张图片的内容然后识别出里面的文字。最直接的思路是用 OCR。JavaScript 里有一些 OCR 库可以用比如 Tesseract.js。它可以在浏览器里直接运行不需要服务器。但 Tesseract.js 的体积比较大加载和识别都需要时间对于“快速填验证码”这个场景来说可能有点慢。另一种思路是针对特定网站的验证码做模板匹配。因为很多系统的算术验证码样式是固定的——字体、字号、颜色、背景都一样只是数字不同。你可以先把 0 到 9 这十个数字的图片模板保存下来然后对验证码图片做逐像素比对找出最匹配的数字。这种方法的准确率很高速度也快但只适用于验证码样式不变的情况。还有一种思路是拦截 Canvas 的绘制指令。如果验证码是通过 Canvas 绘制的你可以在脚本里重写 Canvas 的fillText方法把绘制的内容记录下来。这样你就能直接拿到题目文字完全不需要 OCR。这种方法最优雅但只适用于 Canvas 形式的验证码。我实际项目中用的是模板匹配的方案因为目标系统的验证码样式非常固定模板匹配的准确率接近 100%而且速度极快。下面详细讲一下模板匹配的实现。4.3 模板匹配方案的具体实现模板匹配的核心思路是把验证码图片和预先准备好的数字模板进行逐像素比对找出匹配度最高的数字。首先你需要准备数字模板。方法很简单在目标网页上多刷新几次验证码把每次出现的验证码图片保存下来然后用图片编辑工具把每个数字单独裁剪出来保存成小图片。比如你收集到了“37?”、“52?”、“81?”这几张验证码就可以裁剪出数字 3、7、5、2、8、1 的模板。一般来说收集两三轮就能覆盖 0 到 9 所有数字。然后在脚本里加载这些模板图片把验证码图片也加载进来进行比对。比对的方法是把验证码图片按字符宽度切分成若干个小块每个小块和所有数字模板逐一比对计算像素差异差异最小的那个模板就是识别结果。// 假设模板图片已经以 base64 形式嵌入 const templates { 0: data:image/png;base64,..., 1: data:image/png;base64,..., // ... 其他数字 }; async function recognizeCaptcha(captchaImgElement) { // 把验证码图片绘制到 canvas 上 const canvas document.createElement(canvas); const ctx canvas.getContext(2d); canvas.width captchaImgElement.naturalWidth; canvas.height captchaImgElement.naturalHeight; ctx.drawImage(captchaImgElement, 0, 0); // 获取像素数据 const imageData ctx.getImageData(0, 0, canvas.width, canvas.height); // 按字符切分假设每个字符宽度固定 const charWidth canvas.width / 5; // 假设验证码有5个字符 const results []; for (let i 0; i 5; i) { const charData extractChar(imageData, i * charWidth, 0, charWidth, canvas.height); const bestMatch matchTemplate(charData, templates); results.push(bestMatch); } return results.join(); }这段代码是简化版的逻辑实际实现中还需要处理图片的二值化、去噪、字符分割等细节。但核心思路就是这样把图片转成像素数据按字符切分和模板比对找出最匹配的结果。4.4 从识别结果到自动填写的完整链路识别出验证码内容后下一步就是解析算术表达式并计算结果然后填入输入框。假设识别结果是“37”你需要提取出数字和运算符计算出答案。这部分逻辑和文字型验证码的处理是一样的用正则表达式提取即可。function solveArithmetic(expression) { const match expression.match(/(\d)\s*([\-*×÷])\s*(\d)/); if (!match) return null; const num1 parseInt(match[1], 10); const operator match[2]; const num2 parseInt(match[3], 10); switch (operator) { case : return num1 num2; case -: return num1 - num2; case *: case ×: return num1 * num2; case ÷: return Math.floor(num1 / num2); default: return null; } }计算出答案后找到页面上的输入框把答案填进去并触发必要的事件。function fillAnswer(answer) { const input document.querySelector(#captcha-answer); if (!input) return; input.value answer; input.dispatchEvent(new Event(input, { bubbles: true })); input.dispatchEvent(new Event(change, { bubbles: true })); // 有些页面需要点击提交按钮 const submitBtn document.querySelector(#submit-btn); if (submitBtn) { submitBtn.click(); } }整个链路就是找到验证码元素 → 识别内容 → 解析算术表达式 → 计算结果 → 填入输入框 → 触发事件 → 提交。每一步都需要根据目标网页的具体结构做适配。5. 脚本调试与问题排查实战中踩过的坑5.1 验证码元素找不到怎么办这是最常见的问题。你写好了脚本打开网页发现什么都没发生。打开控制台一看报错说querySelector返回了null。原因通常有几个第一选择器写错了。你可能用了#captcha但实际元素的 ID 是#captcha-image。解决方法是用浏览器的开发者工具按 F12检查元素确认正确的选择器。第二脚本执行时机太早元素还没渲染出来。解决方法是在脚本里加一个轮询机制每隔一段时间检查一次元素是否存在。function waitForElement(selector, timeout 10000) { return new Promise((resolve, reject) { const startTime Date.now(); const timer setInterval(() { const el document.querySelector(selector); if (el) { clearInterval(timer); resolve(el); } else if (Date.now() - startTime timeout) { clearInterval(timer); reject(new Error(Element ${selector} not found within ${timeout}ms)); } }, 200); }); }第三元素在 iframe 里面。如果验证码是在一个 iframe 中加载的你需要先获取 iframe 的contentDocument然后在里面查找元素。跨域的 iframe 无法直接访问这种情况就比较棘手可能需要用grant申请特殊权限。5.2 识别准确率不高的优化方法模板匹配的准确率受很多因素影响。图片的清晰度、字符的粘连程度、背景噪声、字体变化都会影响识别效果。我实际调试时发现最关键的一步是图片预处理。原始验证码图片往往带有干扰线、噪点、背景渐变等直接比对效果很差。需要先做二值化处理——把图片转成黑白两色去掉灰色过渡。然后做去噪处理——把孤立的噪点像素去掉。最后做字符分割——把粘连的字符切开。二值化的阈值选择很关键。阈值太高字符会断裂阈值太低噪点会保留。我一般用 Otsu 算法自动计算阈值效果比固定阈值好很多。function binarize(imageData, threshold) { const data imageData.data; for (let i 0; i data.length; i 4) { const gray 0.299 * data[i] 0.587 * data[i1] 0.114 * data[i2]; const value gray threshold ? 255 : 0; data[i] data[i1] data[i2] value; } return imageData; }如果模板匹配的准确率始终上不去可以考虑换用 Tesseract.js 做 OCR。虽然慢一点但对字体变化的适应性更好。或者如果目标系统的验证码是动态生成的可以考虑直接拦截生成验证码的 JavaScript 函数从源头获取题目内容完全绕过图像识别。5.3 页面框架导致的事件触发问题现代网页很多是用 React、Vue、Angular 等框架构建的。这些框架有自己的状态管理机制直接修改 DOM 元素的value属性框架可能检测不到变化。我踩过的一个坑是脚本把答案填进了输入框页面上也显示出来了但点击提交按钮后系统提示“验证码错误”。原因是框架的内部状态没有更新提交时用的是旧的状态。解决方法是手动触发input和change事件。但有些框架还需要更底层的事件比如keydown、keyup。最保险的做法是模拟完整的键盘输入过程function simulateTyping(input, text) { input.focus(); input.value ; for (const char of text) { input.value char; input.dispatchEvent(new KeyboardEvent(keydown, { key: char, bubbles: true })); input.dispatchEvent(new KeyboardEvent(keypress, { key: char, bubbles: true })); input.dispatchEvent(new Event(input, { bubbles: true })); input.dispatchEvent(new KeyboardEvent(keyup, { key: char, bubbles: true })); } input.dispatchEvent(new Event(change, { bubbles: true })); input.blur(); }这种方法模拟了真实的键盘输入过程几乎所有框架都能正确识别。5.4 常见问题速查表问题现象可能原因解决方法脚本完全不执行match规则不匹配检查match的域名和路径是否正确元素找不到选择器错误或时机太早用开发者工具确认选择器加轮询等待识别结果错误图片预处理不够加强二值化和去噪调整阈值填入答案后提交失败框架状态未更新模拟完整键盘输入事件脚本执行一次后失效页面是单页应用路由切换监听 URL 变化重新执行脚本验证码刷新后脚本不工作验证码是动态加载的监听 DOM 变化重新识别6. 脚本的优化与扩展从能用变成好用6.1 性能优化减少不必要的计算模板匹配虽然快但如果每次页面加载都重新加载模板图片、重新计算也会有不必要的开销。优化的思路是缓存。模板图片只需要加载一次之后缓存在内存里。验证码的识别结果也可以缓存——如果验证码图片的 URL 没变说明题目没变不需要重新识别。const templateCache new Map(); const resultCache new Map(); async function getTemplate(digit) { if (templateCache.has(digit)) { return templateCache.get(digit); } const template await loadImage(templates[digit]); templateCache.set(digit, template); return template; }另外如果页面上有多个验证码比如登录和注册各一个可以并行处理减少总耗时。6.2 健壮性提升处理各种边界情况实际使用中会遇到各种意外情况。比如验证码图片加载失败、识别结果为空、计算结果为负数、输入框被禁用等等。脚本需要对这些情况做处理不能一遇到异常就崩溃。我的做法是在关键步骤加 try-catch并记录日志。如果识别失败可以自动刷新验证码重试。如果连续失败多次就停止自动处理让用户手动介入。async function handleCaptchaWithRetry(maxRetries 3) { for (let i 0; i maxRetries; i) { try { const result await recognizeAndSolve(); if (result ! null) { fillAnswer(result); return true; } } catch (e) { console.warn(Captcha attempt ${i 1} failed:, e); } // 刷新验证码后重试 await refreshCaptcha(); await sleep(500); } console.error(All captcha attempts failed, please handle manually); return false; }6.3 可维护性让脚本适应页面变化目标网页随时可能改版验证码的样式、元素的位置、输入框的 ID 都可能变。如果脚本写得太死页面一改就失效。提升可维护性的方法是把配置项抽出来放在脚本头部。比如选择器、模板图片、重试次数等都做成可配置的。这样页面改版时只需要改配置不需要改逻辑。const CONFIG { selectors: { captchaImage: #captcha-img, captchaInput: #captcha-input, submitButton: #login-btn, refreshButton: #refresh-captcha }, maxRetries: 3, retryDelay: 500, templates: { 0: data:image/png;base64,..., // ... } };另外给脚本加上版本号和更新日志方便追踪修改。Tampermonkey 支持自动更新脚本如果你把脚本发布到某个可访问的地址可以配置updateURL和downloadURL让脚本自动更新。6.4 扩展思路从算术验证码到更通用的场景这个项目的核心思路——识别页面上的内容、处理、回填——可以扩展到很多其他场景。比如自动填写表单识别页面上的表单字段从本地存储或配置中读取对应的值自动填入。自动点击确认按钮识别弹窗内容根据预设规则决定是否点击确认。自动提取数据识别页面上的表格或列表提取结构化数据并保存。甚至可以把算术验证码的处理逻辑封装成一个通用的库支持多种验证码类型文字、图片、Canvas通过配置切换。这样以后遇到新的验证码形式只需要加一个识别器不需要重写整个脚本。我后来把这个脚本的核心逻辑抽出来做成了一个小的工具库放在自己的脚本管理器里。每次遇到新的系统需要处理验证码只需要改一下选择器和模板几分钟就能适配好。这种“一次投入、多次复用”的思路是油猴脚本开发中非常值得培养的习惯。7. 一些实操心得与避坑建议写油猴脚本处理验证码这件事技术难度其实不高但细节特别多。我踩过的坑包括但不限于选择器写错导致脚本静默失败、事件没触发导致提交无效、图片跨域导致 Canvas 无法读取像素、模板图片尺寸不匹配导致识别率暴跌、页面是单页应用导致脚本只执行一次就失效。最大的心得是先手动做一遍再让脚本做。在写脚本之前先用浏览器的开发者工具手动操作一遍——找到验证码元素、读取内容、计算答案、填入输入框、点击提交。把每一步的选择器和操作都确认清楚然后再把这些操作翻译成 JavaScript 代码。这样能避免很多“想当然”的错误。另一个建议是从简单场景开始。如果你要处理的验证码是纯文字形式的就先只做文字形式的。跑通之后再考虑图片形式的。不要一上来就搞最复杂的方案容易卡住。还有一点注意合规使用。油猴脚本是一个工具用它来自动化自己日常的重复操作是合理的但不要用它来绕过正常的安全机制或者对他人系统造成负担。我写这个脚本的初衷是减少自己每天登录内部系统的重复劳动仅此而已。最后分享一个调试小技巧在脚本里加一个“调试模式”开关打开时会在控制台输出详细的日志包括每一步的识别结果、计算结果、填入的值等。这样出问题时能快速定位。调试模式默认关闭不影响正常使用。const DEBUG false; function log(...args) { if (DEBUG) { console.log([CaptchaScript], ...args); } }这个脚本我从零开始写前后大概花了两个周末的时间。第一个周末搭环境、写文字型验证码的处理逻辑第二个周末搞图片识别、调模板匹配、处理各种边界情况。现在它已经稳定运行了大半年每天帮我省下几十次手动算验证码的时间。如果你也在被类似的重复操作困扰不妨从这个项目入手学一点油猴脚本开发你会发现它能做的事情远比你想象的多。