1. 从零上手油猴脚本:一个自动处理算术验证码的完整实践
很多人第一次听到“油猴脚本”这个词,脑子里浮现的可能是某种黑客工具,或者觉得这玩意儿离自己特别远。其实说白了,油猴脚本就是一段运行在浏览器里的 JavaScript 代码,它能在你打开某个网页的时候自动帮你干一些事情——比如把页面上的广告藏起来、把按钮变大、自动填表单,或者像我这次要聊的,自动把算术验证码算出来填进去。
我接触油猴脚本的契机很偶然。那阵子我经常需要在一个内部测试平台上反复提交数据,每次提交前都要回答一道“3 + 7 = ?”这样的算术题。题目本身不难,但架不住次数多,几十次下来人就烦了。于是我就想,能不能让浏览器自己把这道题算出来填进去?顺着这个思路摸索了一阵,发现油猴脚本正好能干这事,而且门槛比我想象的低得多——只要你会一点点 JavaScript 基础,甚至只会复制粘贴加改数字,就能跑起来。
这篇文章就是把我从零开始折腾油猴脚本的整个过程整理出来。我会讲清楚油猴脚本到底是什么、怎么安装、怎么定位页面上的算术题、怎么把答案填回去,以及中间踩过的那些坑。适合完全没有接触过油猴脚本、但想试着写一个自己用的小工具的人。不需要你有多深的编程功底,能看懂基本的加减乘除和变量赋值就够了。
2. 油猴脚本到底是什么:先搞懂原理再动手
2.1 油猴脚本的运行机制
油猴脚本的正式名字叫 UserScript,它本质上是一段 JavaScript 代码,通过一个浏览器扩展(最常见的是 Tampermonkey,中文常叫“篡改猴”)来管理和注入。这个扩展会在你访问网页的时候,根据你设定的规则,把对应的脚本代码“塞”进网页里执行。网页本身并不知道这段代码是外来的,它就像网页自己的一部分一样运行。
打个比方,网页是一栋已经装修好的房子,油猴脚本就是你偷偷带进去的一套智能家居设备。房子还是那栋房子,但灯可以自动亮了、窗帘可以自动拉了。你不需要改动房子的结构,只需要在门口(扩展)登记一下,说“我进这栋房子的时候,帮我把这套设备装上”。
这个机制决定了油猴脚本的几个特点:第一,它运行在网页的上下文里,能直接读取和修改页面上的元素;第二,它不需要你拥有网站的源代码,也不需要网站提供任何接口;第三,它只在你自己的浏览器里生效,不会影响别人。
2.2 为什么选油猴脚本而不是别的方案
面对“自动填算术题”这个需求,其实有好几种技术路线可以选。我当初也对比过,最后选油猴脚本是有原因的。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 油猴脚本 | 轻量、即写即用、不需要额外环境 | 只在自己浏览器生效、依赖页面结构 | 个人自动化、小工具 |
| 浏览器控制台手敲 | 零安装 | 每次刷新都要重敲、无法持久 | 一次性调试 |
| 自动化测试框架 | 功能强大、可跨页面 | 环境配置复杂、学习曲线陡 | 大规模自动化测试 |
| 后端接口直接调用 | 效率最高 | 需要逆向接口、门槛高 | 有开发能力的场景 |
对于我这种“就想让自己浏览器自动填个答案”的需求,油猴脚本是最合适的。它不需要我装 Python、不需要配驱动、不需要分析网络请求,打开编辑器写几行代码,保存,刷新页面就能看到效果。这种即时反馈对新手特别友好。
2.3 算术验证码的常见形态
在动手写代码之前,得先搞清楚要对付的算术题长什么样。我观察下来,常见的算术验证码大概有这么几种形态:
- 纯文本形式:页面上直接显示“5 + 3 = ?”,答案输入框就在旁边。这种最好处理,直接读文本、解析数字、计算、填回去。
- 图片形式:算术题被渲染成一张图片,比如“7 × 6 = ?”画在 canvas 或 img 里。这种就麻烦一些,要么做图像识别,要么想办法拿到图片背后的原始数据。
- 动态生成形式:题目由 JavaScript 动态生成,每次刷新数字都变,但生成逻辑在页面代码里能找到。
我遇到的是第一种纯文本形式,这也是最适合新手练手的类型。后面我会重点讲这种,同时也会提一下如果遇到图片形式该怎么考虑。
3. 开发前的准备工作:环境、工具与基础知识
3.1 安装脚本管理器
第一步是给浏览器装一个脚本管理器。市面上有好几个选择,我用的是 Tampermonkey,它在主流浏览器上都有对应的扩展。安装过程很简单,打开浏览器的扩展商店,搜索名字,点安装就行。安装完成后,浏览器工具栏上会出现一个小图标,点开能看到管理面板。
这里有个细节要注意:不同浏览器对扩展的支持程度不一样。有些浏览器对扩展的审核比较严,可能搜不到或者功能受限。如果遇到这种情况,可以换一个对扩展支持更友好的浏览器来开发调试。我个人的习惯是开发阶段用一个浏览器,日常使用用另一个,避免脚本互相干扰。
安装好之后,建议先花几分钟熟悉一下管理面板。面板里能看到已安装的脚本列表、每个脚本的启用状态、以及一个代码编辑器。后面我们写的所有代码都在这个编辑器里完成。
3.2 需要掌握的 JavaScript 基础
油猴脚本用的是 JavaScript,但你不需要成为 JS 高手。对于自动填算术题这个任务,掌握下面这几个概念就够用了:
- 变量:用来存东西的容器,比如
let answer = 10; - 函数:一段可以重复使用的代码块,比如
function add(a, b) { return a + b; } - DOM 操作:读取和修改网页元素,比如
document.querySelector('.question') - 事件:页面加载完成、按钮被点击等时机,比如
window.addEventListener('load', ...) - 字符串处理:从一段文字里提取数字,比如
parseInt('5 + 3')
如果你之前完全没接触过编程,建议花一两个小时看一下 JavaScript 的基础语法。不用看太深,把变量、函数、条件判断、循环这几个概念搞清楚就行。网上有很多免费的入门教程,挑一个看得顺眼的跟着敲一遍。
3.3 调试工具的准备
写脚本免不了要调试,浏览器自带的开发者工具就是最好的帮手。按 F12 或者右键选择“检查”就能打开。里面有几个面板特别有用:
- Elements(元素):查看页面的 HTML 结构,找到算术题对应的元素
- Console(控制台):运行 JavaScript 代码、查看输出和报错
- Sources(源代码):查看页面加载的脚本文件
我调试的时候,最常用的流程是:先在 Elements 面板里找到算术题的元素,看看它的标签名、class、id 是什么;然后在 Console 里试着用document.querySelector把它选出来,确认选择器写对了;最后再把这些选择器用到油猴脚本里。
提示:在 Console 里测试选择器的时候,如果返回
null,说明选择器没匹配到元素。这时候要检查是不是 class 名字写错了,或者元素在 iframe 里、在 shadow DOM 里,这些情况需要特殊处理。
4. 核心实操:一步步写出自动答题脚本
4.1 新建脚本与元数据块
打开 Tampermonkey 的管理面板,点击“添加新脚本”,会看到一个默认的模板。模板最上面是一段以// ==UserScript==开头、// ==/UserScript==结尾的注释块,这叫元数据块。它告诉管理器这个脚本叫什么名字、在哪些网站上运行、什么时候运行。
// ==UserScript== // @name 自动处理算术验证码 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 自动读取页面上的算术题并填写答案 // @match *://*/* // @grant none // ==/UserScript==这里重点说一下@match这一行。它决定了脚本在哪些网址上生效。*://*/*表示在所有网站上运行,这样写方便测试,但实际用的时候最好改成具体的网址,避免在不相关的页面上瞎跑。比如你的目标页面是https://example.com/form,那就写成@match https://example.com/form*。
@grant none表示脚本不需要特殊的权限,直接在当前页面环境里运行。对于读写页面元素这种操作,none就够了。
4.2 定位算术题元素
脚本要做的第一件事是找到页面上的算术题。假设页面结构是这样的:
<div class="captcha"> <span class="question">5 + 3 = ?</span> <input type="text" id="answer" /> <button id="submit">提交</button> </div>那么用document.querySelector就能把题目文字取出来:
const questionEl = document.querySelector('.captcha .question'); const questionText = questionEl.textContent; // "5 + 3 = ?"但实际页面往往没这么规整。可能 class 名字是随机生成的,可能题目和输入框不在同一个父元素下,可能页面上有多个类似的元素。这时候就需要多试几种选择器,或者用更宽泛的匹配方式。
我遇到的一个情况是,题目文字和输入框之间隔了好几层 div,而且 class 名字每次刷新都会变。后来我发现题目文字总是包含“= ?”这个特征,于是改用遍历的方式:
const allSpans = document.querySelectorAll('span'); let questionText = ''; for (const span of allSpans) { if (span.textContent.includes('= ?')) { questionText = span.textContent; break; } }这种“按内容特征找元素”的思路,在页面结构不稳定的时候特别管用。
4.3 解析算术表达式并计算
拿到题目文字之后,下一步是把里面的数字和运算符提取出来。题目格式是“5 + 3 = ?”,我们需要得到 5、+、3 这三个信息。
最简单的方法是用正则表达式:
const match = questionText.match(/(\d+)\s*([+\-×*])\s*(\d+)/); if (match) { const num1 = parseInt(match[1]); const operator = match[2]; const num2 = parseInt(match[3]); let answer; switch (operator) { case '+': answer = num1 + num2; break; case '-': answer = num1 - num2; break; case '×': case '*': answer = num1 * num2; break; } }这里有几个细节要注意。第一,乘号可能是×也可能是*,甚至可能是x,正则里要把常见写法都覆盖到。第二,减号的结果可能是负数,要确认输入框是否接受负号。第三,有些题目的数字可能是多位数,\d+能匹配一位或多位数字。
如果题目里包含除法,还要考虑除不尽的情况。不过大多数算术验证码为了简单,一般只用加减乘,除法出现得比较少。
4.4 把答案填回输入框
算出答案之后,要把它填到输入框里。这里有个坑:直接设置input.value有时候不生效,因为有些页面会监听输入事件,只有通过模拟用户输入才能触发页面的校验逻辑。
const inputEl = document.querySelector('#answer'); inputEl.value = answer; inputEl.dispatchEvent(new Event('input', { bubbles: true })); inputEl.dispatchEvent(new Event('change', { bubbles: true }));dispatchEvent这两行就是手动触发 input 和 change 事件,让页面以为用户真的输入了内容。如果不加这两行,有些页面提交的时候会提示“请输入答案”,即使输入框里明明有值。
4.5 处理页面加载时机
油猴脚本默认在页面加载的早期就执行了,这时候算术题的元素可能还没渲染出来。如果直接去查元素,会得到null。解决办法是等页面加载完成再执行:
window.addEventListener('load', function() { // 这里放主要逻辑 });但load事件要等所有资源(图片、样式表等)都加载完才触发,有时候太慢了。更精细的做法是用DOMContentLoaded,或者用MutationObserver监听元素出现。
我实际用下来,最省事的方案是加一个轮询:每隔几百毫秒检查一次元素是否存在,存在就执行,执行完就停止轮询。
const timer = setInterval(function() { const questionEl = document.querySelector('.captcha .question'); if (questionEl) { clearInterval(timer); // 执行主要逻辑 } }, 300);这个方案的好处是不依赖特定的事件,不管页面是同步渲染还是异步加载,只要元素最终会出现,就能被捕获到。
5. 进阶话题:当算术题变成图片怎么办
5.1 图片验证码的难点
前面讲的都是纯文本算术题,但现实中很多网站会把算术题渲染成图片。这时候textContent拿到的是空字符串,因为图片里没有文字节点。面对图片验证码,有几条路可以走:
- 图像识别:用 OCR 技术把图片里的文字识别出来。这需要额外的库,而且识别率受图片质量影响。
- 找图片背后的数据:有些网站生成图片的时候,答案其实藏在页面的 JavaScript 变量里,或者藏在图片的 alt 属性、data 属性里。
- 模拟请求:分析页面获取验证码的请求,直接拿到题目数据。这需要一定的网络分析能力。
对于新手来说,我建议先看看图片元素有没有携带额外信息。比如:
<img src="/captcha.png" alt="7 + 2 = ?">const img = document.querySelector('.captcha img'); img.style.transform = 'scale(2)'; img.style.border = '2px solid red';这种方案适合那些“题目本身不难,就是找起来费劲”的场景。
6. 常见问题与排查技巧实录
6.1 脚本不生效的排查顺序
脚本写完保存了,刷新页面却没反应,这是最常见的情况。我总结了一个排查顺序,基本能覆盖大部分问题:
- 确认脚本已启用:打开 Tampermonkey 面板,看看脚本前面的开关是不是绿色的。
- 确认 @match 匹配当前网址:如果 @match 写的是特定网址,而你测试的页面不在这个范围内,脚本不会运行。
- 打开控制台看报错:按 F12 看 Console 面板,如果有红色报错,说明代码有问题。
- 检查选择器是否匹配到元素:在 Console 里手动运行
document.querySelector('你的选择器'),看返回什么。 - 检查执行时机:如果元素是异步加载的,脚本可能在元素出现之前就执行完了。
6.2 常见问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 脚本完全不运行 | @match 不匹配、脚本未启用 | 检查 @match 和启用状态 |
| 报错 querySelector 返回 null | 选择器写错、元素未加载 | 在 Console 验证选择器、加轮询 |
| 答案填了但提交失败 | 未触发 input 事件 | 手动 dispatchEvent |
| 答案算错 | 正则没匹配对、运算符判断遗漏 | 打印中间变量调试 |
| 刷新后失效 | 页面结构变化、class 随机 | 改用内容特征匹配 |
| 脚本影响其他页面 | @match 范围太宽 | 缩小 @match 范围 |
6.3 几个实用的调试技巧
第一个技巧是善用console.log。在脚本的关键位置打印变量,比如题目文字、解析出的数字、算出的答案,这样能快速定位是哪一步出了问题。
第二个技巧是在 Console 里直接测试代码片段。不用每次都改脚本、保存、刷新,直接在 Console 里敲几行验证思路,确认没问题再写进脚本。
第三个技巧是给脚本加一个显眼的标记。比如在页面角落加一个红色小方块,脚本运行了就显示,没显示就说明脚本没跑起来。这比翻控制台快多了。
const marker = document.createElement('div'); marker.textContent = '脚本已运行'; marker.style.cssText = 'position:fixed;top:0;right:0;background:red;color:white;padding:4px;z-index:9999;'; document.body.appendChild(marker);6.4 避坑经验:页面结构变化与脚本维护
油猴脚本有一个天然的脆弱性:它依赖页面的结构。网站一改版,class 名字一换,脚本可能就失效了。我踩过几次坑之后,总结了几条经验:
- 尽量用稳定的特征做选择器:id 通常比 class 稳定,语义化的属性比随机生成的 class 稳定。
- 加容错处理:如果找不到元素,不要让脚本报错崩溃,而是安静地退出或者重试。
- 写注释:过几个月回头看自己的脚本,没有注释的话很可能看不懂当初为什么这么写。
- 版本管理:每次修改在元数据块里更新 @version,方便回溯。
注意:油猴脚本只应该用于你自己有权限操作的页面。不要用它去干扰别人的正常使用,也不要用它绕过任何合理的访问控制。技术是用来提高效率的,不是用来搞破坏的。
7. 从这个小项目能延伸出什么
自动处理算术验证码只是一个切入点,油猴脚本能做的事情远不止这些。把这次学到的技能拆开来看,其实包含了几个通用的能力:定位页面元素、读取和修改内容、处理异步加载、调试和排错。这些能力组合起来,可以做出很多实用的小工具。
比如你可以写一个脚本,把某个经常访问的页面上的关键信息提取出来,整理成更易读的格式;或者写一个脚本,自动展开那些默认折叠的内容;再或者写一个脚本,给页面上的表格加上排序和筛选功能。思路都是一样的:观察页面、找到规律、用代码把重复劳动自动化。
我个人的体会是,油猴脚本最大的价值不在于它能做多复杂的事情,而在于它的门槛足够低,低到你可以为了省几分钟的重复操作就去写一个。这种“随手造工具”的习惯一旦养成,你会发现很多日常的电脑操作都可以被优化。刚开始可能只是自动填个验证码,慢慢地你会开始想:这个流程能不能自动化?那个重复点击能不能省掉?这种思维方式的转变,比学会某个具体的 API 更有价值。
最后分享一个小技巧:如果你写了一个脚本觉得好用,可以把它导出成.user.js文件备份起来。Tampermonkey 的面板里有导出功能,导出的文件可以在其他浏览器或电脑上导入,换设备的时候特别方便。另外,元数据块里的@description和@name写清楚一点,脚本多了之后才好找。