直接用 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 // /UserScriptname是脚本在管理面板里显示的名字别用重名否则更新时会互相覆盖。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[typesearch], input[nameq]); 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://hostpath但有些特殊场景要注意路径末尾写不写/*差别很大。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 版本至少稳定可控。还有一点页面如果启用了严格的 CSPgrant 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 脚本之间反复横跳思维转不过来。其实脚本编写本质上是三件事确定的触发条件、可重复的执行逻辑、明确的退出结果。油猴里对应的是matchrun-atsetIntervalShell 里对应的是 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 文件。换电脑、换浏览器、换系统的时候直接恢复备份不到半分钟就能还原全部环境。这个操作比一个个重装脚本、重新配置匹配规则崩溃得多。真的我已经靠这招在三个浏览器之间无缝切换了推荐你每次都记得备份尤其是脚本积累多了之后。