☰
Tampermonkey油猴脚本实战:从结构、API到常见坑全解析
2026/10/10 15:08:59 网站建设 项目流程

直接用 Tampermonkey 写了一段时间脚本,踩过各种“页面没反应”“脚本不生效”“权限被卡”的坑之后,还是觉得这东西在浏览器自动化里是绕不开的存在。油猴脚本本质上就是一个运行在浏览器里的 JS 脚本容器,它能帮你在任意网页加载或运行时注入指定逻辑,小到自动翻页、去广告、改样式,大到写数据抓取、做页面功能增强、跑设备老化测试,统统能用一个脚本搞定。这篇文章不会讲太多虚的,直接从脚本结构、API、匹配规则、常见报错这几个角度拆开,适合刚接触油猴、或者已经在写但总被各种怪问题卡住的朋友参考。

1. 项目定位与脚本基础结构拆解

1.1 为什么选 Tampermonkey,而不是直接在控制台写代码

很多人会问,浏览器 F12 打开控制台,直接跑一段 JS 不也能改页面吗?确实能,但控制台代码是一次性的,刷新页面就没了。真正要做长期、周期性、跨页面生效的自动化动作,必须有一个“能随页面自动加载、有独立权限管理、能跨域请求、还能持久化存储”的容器,这就是 Tampermonkey 的核心价值。

Tampermonkey 在国外用户习惯里叫 User Script Manager,在国内就是俗称的“油猴”。同类工具还有 Greasemonkey、Violentmonkey,但它们的历史包袱更重,兼容性和现代浏览器适配都不如 Tampermonkey 省心。在我实际使用中,Tampermonkey 的脚本管理界面、更新机制、云同步、黑白名单、定时触发这些东西都比较顺手,特别适合做长期维护的脚本。

我更看重它的沙箱机制。网页本身可能加载了 CSP 安全策略,或者页面自带各种严格的环境限制,直接在控制台抢注变量、改原型方法很容易被覆盖。油猴脚本运行在隔离上下文里,还能通过 unsafeWindow 按需访问页面全局对象,这比裸着写要稳得多。

1.2 一段脚本文件的基本结构:元数据块是关键

入门写油猴脚本,最先看到的永远是文件头那一堆// ==UserScript==注释块。很多人以为这只是给插件看的信息,随便写写就行。这是个误区,元数据块的每个字段都直接影响脚本的加载范围和时机,尤其要搞清楚以下几个:

// ==UserScript== // @name 页面标题增强 // @namespace 自己的命名空间 // @version 1.0.0 // @description 在页面标题后面追加当前时间 // @author yourname // @match https://example.com/* // @grant unsafeWindow // @run-at document-end // ==/UserScript==

@name是脚本在管理面板里显示的名字,别用重名,否则更新时会互相覆盖。@namespace用来区分同一个名字来自不同作者,一般填你自己网站的域名或者 GitHub 地址,没有固定格式,但最好全项目保持一致。@version一定要养成递增的好习惯,不然你改了脚本内容,浏览器端却因为版本号没变、缓存不刷新而一直跑旧代码,这类问题排查起来特别容易让人怀疑人生。

@match是脚本能生效的网址匹配规则,支持*通配符,也支持一部分特殊语法。比如https://example.com/*只匹配该域名下的全部路径;https://*.example.com/*则匹配子域名。这个字段写错,脚本根本不会在那个页面运行,而且不会报任何错,你只会纳闷“怎么没反应”。所以新脚本做好之后,第一件事就是确认匹配规则正确。

1.3 常用 API 与 GM_ 系列功能速览

Tampermonkey 提供了一系列GM_开头的 API,它们属于脚本管理器额外给的能力,普通页面 JS 没有。最常见的几个:

  • GM_setValue / GM_getValue:在本地存储自定义键值,适合存配置项、状态记录。脚本卸载后这些值默认还在,除非手动清理。
  • GM_addStyle:往页面注入 CSS 样式,改界面布局时很省事。
  • GM_xmlhttpRequest:跨域发起 HTTP 请求,不受同源策略限制,是做接口调用、数据抓取的主要手段。
  • GM_notification:桌面弹通知,适合脚本在后台跑完任务后提醒你。
  • unsafeWindow:在隔离环境下拿到页面原始 window 对象,用来调用页面内部函数。

使用这些 API 时,需要在脚本元数据里声明对应的@grant,例如@grant GM_setValue。如果你没声明就调用,脚本引擎会以安全策略为由拦截,并在控制台抛错。这也是新手最容易踩的坑:代码逻辑明明没错,只是因为漏了@grant,函数直接不存在。

2. 实操:三个可以直接照抄的入门脚本示例

2.1 示例一:自动聚焦搜索框

先来一个最简单的,目的是把整个流程跑通,包括安装、编辑、调试和生效。

// ==UserScript== // @name 自动聚焦搜索框 // @namespace https://yourblog.example // @version 0.1.0 // @description 进入页面后自动聚焦到搜索框 // @author yourname // @match https://search.example.com/* // @grant none // @run-at document-end // ==/UserScript== (function () { 'use strict'; const searchInput = document.querySelector('input[type="search"], input[name="q"]'); if (searchInput) { searchInput.focus(); } })();

这里@grant none表示不调用任何 GM API,脚本直接在页面上下文中运行,最轻量。@run-at document-end表示等 DOM 解析完成后才执行,避免因为元素还没渲染出来而找不到节点。如果你的目标页面是单页应用,DOM 结构可能是异步生成的,那还需要配合MutationObserver监听节点变化,等搜索框出现后再聚焦。

实际使用中,直接querySelector找元素经常碰到选择器变化的情况。我的习惯是先打开控制台,用document.querySelectorAll('input')把所有输入框列出来,逐个确认目标元素的结构、类型、可见性,再写进脚本里。这一步看着多余,能省掉后面大量改选择器的时间。

2.2 示例二:给页面注入样式并标记外部链接

页面上有很多外部链接,你想让它们在视觉上更明显,同时点击前弹出确认提示。这里同时用到了GM_addStyle和事件委托。

// ==UserScript== // @name 外部链接标注器 // @namespace https://yourblog.example // @version 0.2.0 // @description 标注并确认外部链接 // @author yourname // @match https://news.example.com/* // @grant GM_addStyle // @run-at document-end // ==/UserScript== (function () { 'use strict'; GM_addStyle(` a[target="_blank"] { color: #d33; border-bottom: 1px dashed #d33; } `); document.addEventListener('click', function (e) { const link = e.target.closest('a[target="_blank"]'); if (link && !link.dataset.confirmed) { e.preventDefault(); if (confirm('即将打开外部链接,是否继续?')) { link.dataset.confirmed = 'true'; window.open(link.href, '_blank'); } } }); })();

这里有个关键技巧:用事件委托监听 document 上的点击事件,而不是给每个链接单独绑定。因为页面内容可能是动态渲染的,单独绑定会漏掉后续新增的节点,而事件委托靠事件冒泡,新节点也能被捕获。

注意GM_addStyle需要声明@grant GM_addStyle。有些老教程里用addStyle函数自己封装,逻辑上也行,但现在直接用 GM API 更干净。

2.3 示例三:页面数据定时采集并输出到控制台

这个示例更接近实际工作中的“设备老化测试全自动执行脚本”场景:定时检查页面上的某个指标,记录下来,如果指标异常就弹通知。做网页端自动化巡检的时候,这类脚本特别实用。

// ==UserScript== // @name 页面指标巡检 // @namespace https://yourblog.example // @version 0.3.0 // @description 定时采集页面状态并异常提醒 // @author yourname // @match https://dashboard.example.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_notification // @run-at document-idle // ==/UserScript== (function () { 'use strict'; const interval = 30; // 单位秒 let lastStatus = GM_getValue('lastStatus', ''); function checkPage() { const el = document.querySelector('.status-indicator'); if (!el) return; const text = el.textContent.trim(); if (text !== lastStatus) { GM_setValue('lastStatus', text); if (text.includes('异常')) { GM_notification({ title: '巡检告警', text: '页面进入异常状态:' + text, timeout: 5000 }); } } } setInterval(checkPage, interval * 1000); })();

这里用GM_setValue / GM_getValue保存上一次的状态,避免每次都弹通知,只有状态变化且出现异常时才提醒。实际落地时还可以把状态值和时间戳拼接后写进一个数组,形成一条简易历史记录。配合定时器反复执行,就达到了“全自动执行”的效果。

3. 关键机制解析:匹配、运行时机、权限与跨域

3.1 页面匹配规则怎么写才不会“无效”

油猴脚本不生效,排在第一位的怀疑点是@match规则写错了。@match的基本结构是<scheme>://<host><path>,但有些特殊场景要注意:

  • 路径末尾写不写/*差别很大。https://example.com/只匹配首页,https://example.com/*匹配整个站点。
  • 子域名和中路径的通配符只能替代一层。*不能跨目录层级,也匹配不了端口号。https://example.com/*/detail会匹配/a/detail,但匹配不了/a/b/detail。
  • 如果整个站点都要生效,还有个懒人写法:*://example.com/*,这样 http 和 https 都能匹配。

如果你不想靠记忆,直接在 Tampermonkey 管理面板里打开脚本,点“编辑”,再操作页面,看控制台输出“此脚本不在此页面运行”的诊断信息。这种方式比纯靠猜更快。

3.2 隔离环境与 unsafeWindow 的正确使用方式

Tampermonkey 默认把脚本放到一个独立的沙箱环境里,页面里的window、document是影子对象,而不是页面原来的对象。这样做的目的是隔离冲突:页面代码里的全局变量不会意外污染你的脚本,你的脚本也不会被页面的Object.prototype改造影响。

需要和页面内部交互时,才用unsafeWindow。比如页面有个自己封装的全局 APIwindow.App,你就可以通过unsafeWindow.App.getData()直接调用。但要注意,unsafeWindow是绕过隔离的接口,用了之后你就要承担兼容和冲突风险。不要一上来就什么都从 unsafeWindow 拿,能通过 DOM 和自定义事件解决的,优先走正常渠道。

3.3 @run-at 选错,等于脚本跑了个寂寞

@run-at控制脚本注入页面的时机,常见选项包括document-start、document-body(部分版本支持)、document-end、document-idle。

实测的坑是这样的:如果脚本要在页面加载初期就拦截请求、修改响应头或者监听早期事件,必须用document-start,而且配合@grant某些 API 时,注入时机还会受到限制。如果脚本只是改样式、加按钮、处理已有 DOM,用document-end足够。document-idle理论上在文档加载完成后执行,但页面如果有长时间运行的异步加载任务,实际触发时间可能比你预想的晚,这时候按钮还没渲染出来,容易误判“脚本失效”。

我的建议是,先用document-end把核心逻辑跑通,再去分析要不要提前到document-start。过早注入会导致 DOM 还没构建出来,很多选择器查不到元素。

3.4 @grant、跨域请求与 CSP 的限制

@grant声明的是脚本需要用到的外部权限。声明了GM_xmlhttpRequest,才能发起跨域请求。这个 API 在油猴脚本里经常被拿来抓接口数据,本质上是浏览器扩展级别的跨域能力,不受页面 CSP 限制。

但它也有自己的限制,比如必须显式声明域名相关参数,返回值只能通过回调函数拿,不能直接同步返回结果。异步逻辑一旦多起来,就要注意回调嵌套和错误处理。

实际写爬取类脚本时,我会优先用GM_xmlhttpRequest而不是页面自带的fetch。原因很简单:页面里的fetch受跨域策略和 CSP 双重限制,而且页面一个重定向或者跳转,请求可能就被打断了。GM 版本至少稳定可控。

还有一点,页面如果启用了严格的 CSP,@grant none模式下的脚本注入方式可能失效,这时反而需要@grant unsafeWindow或使用管理员模式来绕过部分限制。这类问题在较新的浏览器版本里越来越常见,遇到“脚本代码没错但就是没执行”的情况,可以先把@grant none改成必要的 GM API 再测试。

4. 常见问题与排查技巧实录

4.1 想装第三方脚本,却提示“无法从此网站添加应用扩展或用户脚本”

这个提示在 Chrome 和 Edge 里都遇到过,通常不是因为脚本本身有问题,而是浏览器在阻止网页引导你安装扩展或用户脚本。这种情况一般发生在你访问 GitHub、GreasyFork 等网站,点安装链接时浏览器没识别成有效脚本源。

第一次遇到时别慌,排查顺序如下:

  • 确认你访问的是脚本管理器官方支持的网站。Tampermonkey 对 GreasyFork 的识别是最顺滑的,直接在详情页点“安装此脚本”就能弹到管理器确认界面。
  • 如果是在 GitHub 上打开.user.js文件,浏览器默认把它当成普通 JS 文件显示,不会自动弹出安装。这时候点击地址栏右边 Tampermonkey 扩展图标,再选择“添加新脚本”,把源码整体复制进去即可。
  • 还有一种情况是 Tampermonkey 扩展本身没被浏览器识别为可用的开发者模式扩展。新版 Edge 和 Chrome 对“开发者模式”下的扩展有特殊提示,虽然不影响已安装扩展运行,但某些安装渠道会受阻。

注意:浏览器提示“无法从此网站添加应用扩展或用户脚本”,多数时候是安全策略的正常防御机制。千万别为了省事直接关闭浏览器安全设置,正确做法是改用 Tampermonkey 面板手动导入脚本。

4.2 脚本太大,注入之后游戏页面打不开怎么办

这个问题在复杂页面上非常典型。脚本动不动上千行,各种依赖库直接@require进来,结果页面加载时油猴要把整个 JS 代码注入进去,前端包解析变慢,特别是大型游戏页面、控制台的单页应用,一个脚本拖垮首屏的情况我看过不少。

从“脚本编写”角度绕开它,主要有几个方向:

  • 缩减运行时依赖。能用原生 DOM API 解决的,不要引 jQuery;能用CSS.escape解决的,不要引入完整工具库。
  • 大量样式尽量合并成一个GM_addStyle调用,不要散落几十个<style>标签,减少页面样式计算量。
  • 如果脚本逻辑本身确实很大,那就只把必要的注入代码放在主体脚本里,把耗时的数据处理放到GM_xmlhttpRequest拉取一段独立函数之后执行。这样页面首屏周期内只保留最少的 JS 开销。
  • 开启 Tampermonkey 的“延迟执行”配置,或者把@run-at调到document-idle,让脚本在所有资源加载完成后再进入,避免阻塞页面渲染。

实际项目中还遇到过另一个极端:脚本只用了@require引入一个很大的外链库,比如moment.js、lodash全套,结果页面加载速度慢了 40%。换掉这些仓库级依赖后,体积从 200KB 降到 10KB,问题立刻消失。

4.3 脚本突然失效,不是代码变了,而是页面升级了

页面升级导致脚本失效,是我遇到频率最高的问题。页面框架从纯后端渲染改成前端异步渲染,或者 DOM 类名从语义化变成哈希命名,脚本里写死的选择器全部失效。

排查思路可以按下面这张表走:

现象优先检查解决方案
控制台无任何报错,脚本什么都不做@match、@run-at、页面是否使用了严格 CSP先用管理面板诊断,确认脚本在不在页面权限内
页面上元素存在,但选择器查不到 null目标元素被 iframe、shadow DOM 包裹改用 iframe 遍历或者穿透 shadow root
脚本在控制台执行成功,但页面刷新后状态丢失页面是 SPA,容器节点被整体重新渲染改用MutationObserver监听容器变化,动态执行初始化
控制台报 GM_ 函数找不到漏写 @grant,或脚本模式不对补全对应权限声明

页面升级后,旧脚本失效属于常态,别总觉得是自己代码崩了。先看控制台报错,再确认页面结构,最后试着手动跑一遍核心逻辑,三个步骤走完基本能定位。

4.4 和其他脚本环境的排错共性:先看环境,再看代码

写 Tampermonkey 脚本的过程中,我偶尔也会转到 shell 脚本、Python 脚本和 PowerShell 脚本的排错,发现它们的逻辑惊人地一致。比如新手在 Windows 上执行pip,最常见的报错是“无法将 pip 项识别为 cmdlet、函数、脚本文件或可运行程序的名称”,本质上和油猴里声明的 API 找不到是同一类问题——运行环境里没有配置好对应工具链,或者工具链路径不在系统环境变量里,代码本身可能完全没问题。

这提醒我一个重要经验:脚本出问题时,第一反应不应该是“我的逻辑写错了”,而是先排查执行依赖是否完整。Tampermonkey 脚本依赖的是浏览器、script manager、页面本身这几个层级;shell 脚本依赖的是解释器和命令路径;Python 脚本依赖的是解释器版本、pip 模块安装情况。把“环境是否就绪”这一项放在排查清单第一条,排错速度会快很多。

5. 进阶经验:调试、安全边界、与其他脚本场景的迁移

5.1 断点调试与 console 日志的正确姿势

写简单脚本时,console.log够用。但脚本逻辑复杂以后,只靠 console 不可行。Tampermonkey 脚本是可以直接在浏览器的 Sources 面板里打断点的,方法是打开开发者工具,在“脚本”标签页里找到 Tampermonkey 注入的脚本文件,展开后找到你正在调试的那一段。实际体验是,断点调试比打日志高效十倍,因为你可以直接在作用域面板里看变量状态,不用一遍遍地刷新页面拼日志。

另一个技巧是把日志按前缀分类。我习惯在每条日志前带上脚本名缩写,比如[DASHBOARD]、[SCRAPER],这样在 Console 面板里能一键过滤出特定脚本的输出。多脚本同时跑的时候,这个习惯能省很多眼睛。

GM_log也是个不错的备选,它会在 Tampermonkey 自己的控制台里输出,不受页面重载影响。如果页面刷新频繁导致浏览器控制台内容被清空,用GM_log更适合保留历史。

5.2 脚本的安全边界:别把 XSS 当儿戏

油猴脚本能力太强,跨域请求、操作 DOM、读取页面数据全都沾边,所以它天然是一把双刃剑。搜索词里经常看到“DVWA 跨站脚本存储型”这类实验环境,实际上就是 XSS 攻击的基础形态。Tampermonkey 脚本如果写得不干净,完全可以成为新型 XSS 攻击的载体。

因此,实操原则必须坚持几条:

  • 不要随便安装来源不明的 .user.js 脚本。尤其那些压缩过、代码完全不可读、还要你登录账号、输入密码的脚本,风险极高。
  • 自己开发脚本时,涉及eval、Function动态执行的,能不用就不用。
  • 如果要从页面本身读取内容,必须对内容做字符串处理,防止把恶意脚本直接塞进 innerHTML。
  • 涉及GM_xmlhttpRequest的接口地址,写死来源白名单,不要用用户传入参数直接拼接 URL。

现实中遇到过有人把油猴脚本源码直接放到竞品网页上,导致用户一访问就自动拉取恶意接口。这种脚本从根上就在作恶,靠技术手段只能自保,靠不了别人。所以我不管是大项目还是临时脚本,一律自己写自己审,优先用外链库里声名良好、代码公开的。

5.3 从 Tampermonkey 到 shell、Python、PowerShell 的共性提炼

很多人在“shell 脚本”“Python 脚本”和 Tampermonkey 脚本之间反复横跳,思维转不过来。其实脚本编写本质上是三件事:确定的触发条件、可重复的执行逻辑、明确的退出结果。油猴里对应的是@match+@run-at+setInterval;Shell 里对应的是 cron 触发、命令序列和退出码;Python 里对应的是解释器入口、循环分支和最终输出。

用这个共通框架去读其他环境里的脚本,就不会觉得陌生。比如powershell 开机自启脚本,本质上就是把一个 Python 脚本的入口注册到 Windows 启动项,和油猴脚本让浏览器在打开特定网址时自动执行,是一个套路。区别只是触发条件和执行环境不同。

写 Tampermonkey 时养成的良好习惯,比如日志分类、状态持久化、异常捕捉、环境依赖排查,放到 shell 和 Python 脚本里同样有效。有时候我会同时维护一个 Tampermonkey 巡检脚本和一个 Python 定时任务脚本,逻辑几乎可以平移,差别只在注入页面和读系统资源的不同。

5.4 后续还能这样扩展:油猴脚本能做的四件事

如果基础示例已经跑通,下一步可以考虑这些方向。

第一,把多个脚本按站点分组管理,用 Linterna 配置在不同域名下启用不同脚本,避免一个脚本满站跑。

第二,用GM_getResourceURL和GM_getResourceText引入本地资源文件,比如配置文件、DOM 模板,让脚本主体更干净。

第三,配合外部工具做联动。Tampermonkey 脚本能把页面数据通过 GM API 存到GM_getValue,但更灵活的方式是调用本机端口,把数据发到本地 Python 服务。脱离浏览器限制的数据处理能力会强很多。

第四,把脚本做成配置化。通过管理面板里的用户设置项,让你不用改代码就能调整行为开关、延迟时间、白名单等参数。我自己维护的页面巡检脚本就是这样,所有敏感指标都做成配置项,其他人拿到手只改配置,不动代码。

注意:配置化脚本对“小白友好”这件事帮助很大,但对脚本作者自身要求也高。你必须把入口逻辑和配置读取解耦得足够干净,否则配置项之间互相依赖,后期会变成维护噩梦。

6. 写在最后,仍然是最真实的经验

从我第一次在浏览器里装上 Tampermonkey 到现在,最大的感受就是:脚本能不能跑起来,80% 取决于你对“运行环境”的理解,而不是你写了多漂亮的代码。页面结构变了、浏览器策略变了、Tampermonkey 权限模型变了,任何一项都可能让之前完美的脚本瞬间作废。所以我的日常习惯是:脚本文件头写好版本号和变更记录,每次页面升级后跑一遍核心用例,控制台第一时间发现问题。宁可日志烦一点,也不要留着错误状态猜来猜去。

最后再分享一个小技巧。很多人不知道 Tampermonkey 自带一个“导出”功能,可以把所有脚本和配置打包成 zip 文件。换电脑、换浏览器、换系统的时候,直接恢复备份,不到半分钟就能还原全部环境。这个操作比一个个重装脚本、重新配置匹配规则崩溃得多。真的,我已经靠这招在三个浏览器之间无缝切换了,推荐你每次都记得备份,尤其是脚本积累多了之后。

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

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

立即咨询