☰
油猴脚本从入门到实战:安装、匹配规则与失效排查
2026/10/17 12:09:09 网站建设 项目流程

1. 很多人装完油猴后三天就放弃,卡在哪?

先讲一个我经常遇到的现象:有人听说油猴脚本很强大,可以清广告、自动签到、优化网页,于是装了一个 Tampermonkey 浏览器扩展,兴冲冲打开某个资讯站,结果页面该有的广告还在,悬浮窗还飘着,点哪里都没反应。两小时后对方跟我说,“这东西没用,我卸载了”。

问题出在一个很容易被忽略的认知错位:油猴管理器本身不是一个“功能插件”,它更像一个运行引擎。引擎装好了,但里面没有加载任何“程序”,自然什么都干不了。所谓油猴脚本,本质上就是一段 JavaScript 代码,只不过在开头声明了自己的名称、版本、匹配哪些网址、需要哪些权限,剩下的逻辑和你在网页控制台里写的代码没什么区别。

所以,这篇文章帮大家理清一条完整的使用链路:油猴管理器安装好了之后,下一步去哪里找脚本、怎么装、怎么判断脚本是否生效、匹配规则怎么理解,以及遇到脚本失效时怎么排查。我还会分享一些从零手写脚本的模板和调试技巧,新手可以直接照着做。

适合看这篇文章的人有两类:一类是只想“用脚本”,希望把某些网页操作自动化;另一类是想“写脚本”,借油猴练手,做点提升效率的小工具。这两条路我都走过,下面按实际使用顺序来讲。

2. 从零到第一次看到脚本生效,总共四步

2.1 先把管理器安装到浏览器里

这一步本身不复杂,但有个细节需要提醒:安装完 Tampermonkey 之后,工具栏上会出现一个黑色或灰色的图标,很多浏览器会默认把扩展图标收到“扩展菜单”里,记得手动把它固定出来,否则后面完全找不到入口。

安装完成后,先打开油猴的管理界面,也就是“管理面板”。不同浏览器上的面板布局略有差异,但核心功能都差不多:已安装脚本列表、脚本编辑器、日志面板、设置选项。

如果你用的是基于 Chromium 内核的浏览器,插件兼容性通常是最稳定的。旧版本的某个浏览器如果一直安装失败,检查一下浏览器本身是否需要更新,或者换个浏览器再试。

2.2 从脚本仓库找现成脚本

油猴脚本的获取方式和普通软件不太一样,它不在浏览器的扩展商店里搜索,而是需要在用户脚本分享社区里找到。

在这些平台上,一个脚本通常就是一个详情页,页面上有明显的“安装此脚本”按钮。点击后浏览器会弹出确认窗口,里面能看到脚本的名称、匹配当前网址范围、以及要申请的权限。确认没问题,点“安装”,脚本就算进了你的油猴里。

这里我强调一句:安装完先别急着去各种网站乱逛。我们要看脚本是否生效,得先看它的“匹配规则”。如果脚本规则里写明只在某个路径下生效,那它就不会去管其他网站的事。这是油猴脚本的基本工作方式,不是我安装的某个功能没装上。

2.3 新建一个最小脚本,验证整条链路

如果你一时找不到合适的现成脚本,也可以先新建一个脚本,确认自己的油猴运行环境是通的。操作路径是:管理面板 -> 新建脚本,编辑器会自动生成一个模板。

把模板替换成下面的内容:

// ==UserScript== // @name 页面标题提醒助手 // @namespace local.demo // @version 0.1.0 // @description 打开指定页面后弹出当前页面标题,用来验证油猴运行链路 // @match https://*.example.com/* // @run-at document-idle // @grant none // ==/UserScript== (function () { "use strict"; const title = document.title; alert("当前页面标题是:" + title); })();

保存后,找一个匹配该规则的页面访问。如果你真的访问了一个符合“https://.example.com/”规则的页面,就会看到弹窗。

看到弹窗,说明油猴的链路是通的,问题只出在脚本选择或匹配规则上。这一步很重要,它能帮你把“油猴坏了”和“脚本不会用”区分开。

2.4 管理面板里几个常用按钮别乱点

管理面板里最容易引起误解的是这几个地方:

  • 启用/禁用开关:控制脚本是否运行,不删除代码。
  • 编辑按钮:可以直接修改脚本,保存后刷新页面才会按新版本执行。
  • 删除按钮:会真的把脚本移除,操作前想清楚。
  • 自动更新:很多老手开着,但我个人建议新装的脚本先观察一两天再决定要不要自动更新,原因后面避坑部分会说。

这里再补充一个经验:在脚本详情里看到的“上次更新日期”和“安装量”,是你选择脚本时的重要参考。如果某个脚本已经几年没更新,大概率已经不适配现在的网站结构了,装上也只会让控制台报错,不解决问题。

3. 匹配规则和运行时机,决定脚本在哪儿“醒过来”

3.1 @match 与 @include 的取舍

很多新手写脚本时,最常犯的错误就是元数据里的匹配规则写不对。你写的脚本代码再好,@match配错了,脚本就根本不会执行。

先看匹配规则的主干字段:

字段作用典型写法
@match声明脚本在哪些网址生效,语法遵循浏览器扩展匹配模式https://*.example.com/*
@include更灵活的包含匹配,支持正则表达式/^https?:\/\/(www\.)?example\.com\//
@exclude排除某些网址,优先级高于匹配https://example.com/admin/*
@noframes是否在 iframe 子页面中运行@noframes

@match是大多数场景下最合适的字段,它要求你写清楚协议、域名、路径。比如:

// @match https://*.example.com/*

这里的*.example.com能匹配example.com本身,也能匹配a.example.com,但不能匹配example.org。路径末尾的/*表示该域名下所有路径。

@include更灵活,但我不建议新手一上来就用正则,因为一旦写错,排查成本远高于收益。普通网站直接写@match就够了。@exclude用来排除管理后台、接口路径等不需要脚本执行的地址,它优先级更高,能帮你避免很多误触发。

3.2 运行时机:document-start 和 document-end 有多重要

匹配规则解决“在哪运行”,@run-at则解决“什么时候运行”。

油猴支持四种比较常用的运行时机:

@run-at 取值执行时机典型用途
document-start文档加载的最早期,DOM 还没完整解析注入全局样式、提前拦截请求、修改页面基础结构
document-bodybody 元素出现之后早期插入组件,但此时大量子节点还没生成
document-endDOM 全部解析完成,但可能还没触发 DOMContentLoaded读取节点、绑定事件
document-idleDOMContentLoaded 之后,页面空闲阶段大多数脚本的默认选择

新手最容易忽视这个字段。你在document-start就去查一个#content节点,大概率拿到 null,因为内容还没渲染出来。反过来,你想在页面出现前就隐藏某个广告位,结果等到document-idle才执行,广告已经闪了一下,体验照样不好。

我一般的原则是:改样式用document-start,绑定普通事件用document-end,需要等异步渲染完成且不追求极快响应的,用document-idle。

3.3 @grant 权限和沙箱机制

@grant决定你的脚本能使用哪些“油猴特权 API”。最基础的两个值是none和具体 API 名。

当你在脚本里写GM_setValue、GM_getValue、GM_xmlhttpRequest这类函数时,必须在元数据里声明对应的权限,否则脚本会直接在控制台报错,比如“GM_setValue is not defined”。

如果脚本只是普通 DOM 操作,不需要油猴的特殊能力,就明确写:

// @grant none

此时脚本与页面共享同一个上下文,调试起来最方便,也能直接访问页面里的全局变量。

但要注意,一旦声明了某个 GM 权限,脚本就运行在油猴的沙箱里,它的全局环境会和页面隔离开。这时候如果脚本想读取页面里某个全局变量,反而读不到。所以我看到网上很多教程让新手无脑加@grant none,但实际情况要看你要做什么。

如果你要用跨域请求能力,常见的字段组合是:

// @grant GM_xmlhttpRequest // @connect api.example.com

@connect声明允许该脚本向哪些域名发出请求。这个列表别乱写,只写真正需要的域名,能减少很多安全风险。

4. 可以立刻投入使用的几类实用脚本场景

4.1 页面净化与固定元素清理

这类脚本的使用场景最常见。新闻资讯页面往往有大量干扰元素:右侧悬浮窗、底部弹窗、文章中间插入的组件。油猴脚本可以直接把这些元素隐藏,而且比手动刷新页面清理广告的效率高得多。

如果不想下载现成大脚本,自己用几行 CSS 就能实现一个轻量净化脚本:

// ==UserScript== // @name 页面元素净化小工具 // @namespace local.css.demo // @version 0.1.0 // @description 隐藏指定干扰元素 // @match https://news.example.com/* // @run-at document-start // @grant none // ==/UserScript== (function () { "use strict"; const style = document.createElement("style"); style.textContent = ` .right-side-float, #bottom-popup, .middle-banner { display: none !important; } `; document.head.appendChild(style); })();

这类脚本的思路很简单,难点在于你怎么确认元素选择器。F12 打开开发者工具,选中干扰节点,看它的 class 或 id,再替换进去就行。用document-start的原因是避免页面元素先渲染出来再被隐藏,减少视觉闪烁。

4.2 自动填表与重复性表单操作

第二个高频场景是自动填充。比如某些系统的每日填报,一个月有二十天填的是同样内容,手动操作其实非常浪费时间。

现成脚本里有一部分就是这类表单辅助脚本,但站点结构一变就容易失效。想稳定,可以自己写短逻辑,把上次填写的内容存到GM_setValue里,下次打开页面自动带出。我在第五节会给出整套代码。

这里想先说明一个安全习惯:自动填表脚本要做到“填充但不盲目提交”。哪怕内容一样,表单也可能有隐藏字段随时间变化,直接自动点击提交容易出错。我自己的做法是只自动填充、留一个人工确认提交的步骤,这样数据出问题还能及时发现。

4.3 表格导出和数据整理

网页里有一类非常常见却又很头疼的操作——明明页面展示了一个完整的数据表格,却没有导出按钮。用手动复制粘贴整理成 Excel,遇到几百行数据,会让人崩溃。

油猴脚本可以遍历页面表格,把数据拼成 CSV 内容,再触发浏览器下载。这里有个技术细节:保存 CSV 时要在内容最前面加上\ufeff字符,也就是 UTF-8 BOM,否则 Excel 打开可能乱码。很多人现在告诉你“用 Blob 下载 CSV”,但不说这个细节,实际跑起来才发现巨坑。

第五节的第二个脚本会给出完整实现。

4.4 文本提取与跨页信息汇总

这类脚本平时看起来不起眼,但一旦需要临时处理页面数据,非常有用。比如某次你需要把所有文章标题和链接整理成一份清单,一条条复制粘贴确实累,写一个几行的小脚本,遍历页面里的h2和链接节点,把结果打到控制台,再一次性复制出来,效率高很多。

这种临时脚本不需要考虑通用性,反正它只在匹配的页面里运行,用完删除也不心疼。

4.5 挑选现成脚本时的“四看”原则

从脚本仓库选脚本时,我这里有一套看得比较仔细的标准:

  1. 看最后更新时间。超过一年的脚本,很可能已经不适配当前页面结构。
  2. 看安装量和评论。安装量高、有近期评论的,通常维护状态还行。
  3. 看代码量。几十行的小脚本容易审查,几百上千行的脚本要格外谨慎。
  4. 看请求去向。装好后打开开发者工具的网络面板,观察脚本是否有向无关域名发送数据的行为。

第 4 点尤其重要。部分恶意脚本会利用 GM_xmlhttpRequest 把你在目标站点上的数据回传到第三方服务器。脚本源码里如果出现了奇怪的请求地址,就别用了。

5. 自己动手写第一个带状态保存的脚本

5.1 脚本骨架是怎么组成的

一个标准的用户脚本可以拆成两部分:元数据块和业务逻辑。

元数据块是脚本开头的注释段,虽然注释形式,但油猴会读取它。元数据里写名称、版本、描述、匹配规则、运行时机、权限声明。业务逻辑就是一个 IIFE 包裹的 JavaScript 函数,避免污染全局变量。

以“自动填充每日表单”为例,需求是:打开表单页,把上次填过的内容带入输入框,并且在下一次提交前清空记录,避免下次误用旧数据。

// ==UserScript== // @name 自动填充每日表单 // @namespace local.form.demo // @version 0.2.0 // @description 记录上一次填写内容,在指定页面加载时自动带出 // @match https://portal.example.com/* // @run-at document-idle // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function () { "use strict"; function whenReady(fn) { if (document.readyState !== "loading") { fn(); } else { document.addEventListener("DOMContentLoaded", fn); } } whenReady(() => { const textarea = document.querySelector("#daily-text"); const submitBtn = document.querySelector("#submit-btn"); if (!textarea) { return; } const saved = GM_getValue("lastContent", ""); if (saved) { textarea.value = saved; } textarea.addEventListener("input", () => { GM_setValue("lastContent", textarea.value); }); if (submitBtn) { submitBtn.addEventListener("click", () => { GM_setValue("lastContent", ""); }); } }); })();

几个值得解释的细节:

  • whenReady函数是为了兼容多种运行状态。光靠@run-at document-idle有时还不够,尤其是某些页面脚本会提前读完 DOM,这时候需要再判断一次document.readyState。
  • GM_getValue读取上次保存的内容,第二个参数是默认值,空字符串就代表没有历史记录。
  • 输入框每次触发input事件,就把最新文本存进去。所以即使用户中途改了内容,下次打开也能还原。
  • 点击提交按钮后立刻清空记录。这样不会出现“上次提交的内容又在下一次打开时被旧值填进去”的尴尬情况。

5.2 另一个可复用的脚本:表格导出 CSV

再看一个相对完整的业务脚本。目标:为指定页面右下角添加一个“导出当前表格”按钮,点击把页面里某个表格转换成 CSV 文件下载。

// ==UserScript== // @name 表格导出CSV小助手 // @namespace local.csv.demo // @version 0.1.0 // @description 在页面右下角添加“导出当前表格”按钮,输出为CSV文件 // @match https://data.example.com/* // @run-at document-idle // @grant none // ==/UserScript== (function () { "use strict"; function exportTable(table) { const rows = Array.from(table.rows).map((row) => Array.from(row.cells).map((cell) => cell.innerText.trim()) ); const csv = rows .map((row) => row.map((cell) => `"${cell.replace(/"/g, '""')}"`).join(",")) .join("\n"); // 加 BOM,防止 Excel 打开中文乱码 const blob = new Blob(["\ufeff" + csv], { type: "text/csv;charset=utf-8;", }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "export.csv"; link.click(); URL.revokeObjectURL(url); } setTimeout(() => { const table = document.querySelector("table.data-list"); if (!table) { return; } const btn = document.createElement("button"); btn.textContent = "导出当前表格"; btn.style.cssText = "position:fixed;right:20px;bottom:20px;z-index:99999;padding:8px 16px;cursor:pointer;"; btn.addEventListener("click", () => exportTable(table)); document.body.appendChild(btn); }, 1000); })();

这里有一个实际技巧:CSV 内容里的字段值如果包含逗号、引号、换行,直接拼接会把表格结构搞乱。所以我把每个单元格值都用双引号包起来,内部原有的双引号再替换成两个双引号,这是 CSV 格式的标准转义规则。

按钮用position: fixed固定在右下角,避免破坏原页面布局。如果你用的站点也有表格但选择器不同,把table.data-list改成你页面上实际的选择器即可。

5.3 从“能用”到“好用”的改造思路

很多人学到这一步,第一版脚本往往只是把需求写死在自己的站点上,这没什么问题。但如果你想让它更持久,有两个常见改进方向:

  • 把选择器和配置项抽到脚本顶部一个常量对象里,以后改起来方便。
  • 增加一个“排除条件”,比如某些页面不需要展示按钮,但你不想改匹配规则,可以在逻辑里加if (location.pathname.includes("/detail/")) return;。

6. 动态页面和重复挂载:脚本失效的最常见元凶

6.1 问题现象:为什么一个脚本在普通页面能用,在后台管理页里毫无反应

不少脚本新手会遇到这种情况:在自己写的示例页面里一切正常,放到实际目标站点后,等了半天按钮都没出现,控制台也不报错。原因通常是目标页面是动态加载的,脚本执行的时候,目标 DOM 节点根本还没生成。

单页应用尤其明显。很多前端框架用路由切换页面,整个文档只加载一次,后续靠 JS 更新内容。你的脚本只在document-idle执行一次,那自然是装完就没下文了。

6.2 MutationObserver 的最小可用样板

这个问题的通用解法是使用MutationObserver监听 DOM 变化,等目标节点出现后再初始化脚本逻辑。

一个比较常见的最小样板如下:

const observer = new MutationObserver(() => { const target = document.querySelector("#dynamic-container"); if (target && !target.dataset.inited) { target.dataset.inited = "1"; initScript(target); } }); observer.observe(document.body, { childList: true, subtree: true, });

这里有两个细节值得说明:

  • childList: true表示监听子节点增加或移除,subtree: true表示监听范围覆盖所有后代节点。这样页面任何地方新插入节点,都能触发回调。
  • 我特意给目标节点加了一个dataset.inited标记,避免初始化逻辑被反复执行。

6.3 事件监听器叠加的几个后果

还有一个很隐蔽的问题:如果每次 DOM 变化都重新绑定事件,事件监听器会越积越多。你第一次点击按钮,执行一次回调;第二次点击,可能执行两次;多次刷新或多次触发容器变化后,行为会变得非常混乱。

我的习惯是优先采用事件委托,也就是把监听器挂到稳定的父节点或document上,而不是直接挂在动态生成的子节点上:

document.addEventListener("click", (event) => { const btn = event.target.closest(".export-btn"); if (btn) { exportTable(btn.dataset.tableId); } });

这样即使按钮是动态生成的,也不用反复绑定,正好和MutationObserver搭配使用。

6.4 观察器的资源开销别忽视

监听整个document.body的子树变化,在复杂页面上会产生大量回调,尤其是页面里有高频渲染的图表或滚动表格时,容易有性能影响。如果你是为了一个固定区域,尽量缩小观察范围,而不是监听整个 body。

另外,观察器要在适当时机断开,比如找到目标节点并完成初始化后,在代码里调用observer.disconnect()。否则它会一直盯着页面变化,白白消耗资源。

7. 脚本突然失效时,按这条链路排查

7.1 先确认脚本还是亮的,再确认页面确实匹配

脚本失效,很多人的第一反应是重装或删了重写。我的建议是先做三层确认:

第一层,去管理面板看脚本是否处于启用状态,排除不小心点击了“停用”的情况。

第二层,看当前网址和脚本元数据里的匹配规则是否一致。油猴界面上有时会显示“已在此页面运行”,这句话很能说明问题。如果没有显示,说明匹配规则对不上,脚本当然不执行。

第三层,打开浏览器开发者工具的控制台,看有没有脚本报错。报错通常分两类:一类是页面元素选择器找不到,说明页面结构变了;另一类是变量冲突,说明脚本和页面自身的 JS 发生了环境混杂。

7.2 用控制台日志标记脚本执行阶段

有些脚本在中间某一行出错,导致后半段逻辑全没执行。这时可以在关键位置插入console.log标记,逐段定位:

console.log("[userscript] stage 1: started"); // 某段逻辑 console.log("[userscript] stage 2: after init");

刷新页面后看控制台能走到哪一步,就知道是哪段出了问题。用完后记得删掉这些日志。

7.3 网站改版和脚本更新的适配问题

页面结构一旦改版,老脚本几乎必然失效。这个失效规律没办法避免,因为脚本依赖的是当前 DOM 结构。

此时先看脚本是否在仓库里有新版本,有就更新。没有的话,只能手动调整选择器,把脚本里失效的 class 名换成新页面上的。改完以后,建议在版本号后面加个小版本递增,比如 0.1.0 改成 0.1.1,方便自己日后回忆这次改动。

7.4 自动更新也不是越频繁越好

油猴脚本支持自动从仓库拉取新版本,但我不建议所有脚本都开。个别作者会频繁改需求,某个改动刚好和你自己的使用习惯冲突,新版本反而不好用。

如果你希望脚本稳定,就把自动更新关闭,等自己有空时手动更新,更新前先看看版本变化和评论。脚本编辑界面里通常有更新日志或 GitHub 链接,先读再更新,比盲更新省心。

8. 我长期使用中坚持的几个脚本管理习惯

先说说最基础的一条:尽量装那些能在源码层面看懂在做什么的脚本。不是劝每个人都成为开发者,而是说,至少在安装前花几分钟看下脚本头部元数据和请求域名。如果哪里都读不懂,或者里面藏着一个你不认识的网址,就别用了。脚本运行在你登录态的页面里,风险边界一定要自己把关。

另一个习惯是按需启用。油猴支持为每个脚本设置“包括/排除”规则,我为常用脚本设置只对必要域名生效,浏览器里其他页面的性能就不会受到影响。曾经遇到过有人图方便,给脚本写了一条特别宽的匹配规则,导致每次打开任意站点都要执行一遍脚本里的页面净化逻辑,累人又没有意义。

还有备份。油猴脚本本质是纯文本,我自己在本机建了一个“userscripts”文件夹,把改过的脚本都存一份。每次改动都递增版本号,并把关键改动说明写在描述字段里。这比依赖某个平台云端保存可靠得多。

最后一条,是脚本写完以后要删掉临时测试代码。比如我在验证阶段喜欢用alert弹窗,但如果一个自动化填表脚本还弹窗,就失去自动化意义了。上线前把alert、多余的console.log清理干净,脚本整洁很多,以后回看也不容易迷路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询