Tampermonkey 基础
油猴(Tampermonkey) 是一个浏览器扩展,用来在网页加载时自动注入你自己写的 JavaScript 脚本,也就是所谓的「用户脚本 / UserScript」。
它能做的事情,本质上等于「让浏览器替你做这些事」:
- 网页加载后自动执行一段 JS(去广告、改样式、抓接口数据、填表单……)
- 跨域请求接口(绕过同源限制,拿到你平时拿不到的数据)
- 在页面上加按钮、加菜单、把结果一键复制到剪贴板
- 保存配置和数据,下次打开同一页面自动恢复
本文为基础篇,目标是:装好油猴 → 写出会跑的第一个脚本 → 搞懂元数据块 → 会用几个最常用的 GM API。
目录
- 安装油猴
- 认识脚本管理器界面
- 第一个脚本
- 元数据块(最核心的部分)
- 匹配规则:决定脚本在哪些页面运行
- @run-at:决定脚本什么时候执行
- @grant:决定能不能用 GM API
- 常用 GM API 入门
- 跨域请求 GM_xmlhttpRequest
- 引入外部库 @require / @resource
- 注入 CSS:GM_addStyle
- 本地调试与常见问题
1. 安装油猴
Chrome / Edge / Brave
- 打开扩展商店,搜索 Tampermonkey(认准开发者是「dvdkr」)。
- 也可以用 暴力猴 Violentmonkey,开源免费,API 基本兼容(少部分 API 名字略有差异)。
- 安装完成后,浏览器右上角会出现一个「油猴小猴子」图标,点击它可以打开管理面板。
注意:油猴的权限比较大(要能「读取和更改你在所有网站上的数据」),如果你在公司电脑上装,可能被浏览器策略或安全软件拦截。
Firefox
- 打开
about:addons。 - 点击齿轮图标 → 「附加组件」→ 「附加组件主题」。
- 搜索
Tampermonkey安装。
安装后的第一件事:打开允许用户脚本的开关
Chrome / Edge 用户还需要额外做一步,否则脚本不会生效:
- 打开
chrome://extensions/(Edge 是edge://extensions/)。 - 找到 Tampermonkey,点「详细信息」。
- 打开 「允许用户脚本」 开关。
Firefox 不需要这一步。
2. 认识脚本管理器界面
点扩展图标 → 面板里再点一次小猴子图标(或直接进管理页):
| 标签页 | 作用 |
|---|---|
| 已安装脚本 | 你装过的所有脚本,可勾选启用/禁用、编辑、删除、设置「用户脚本权限」 |
| 实用工具 | 在任意页面执行临时代码、抓取页面资源、查看站点列表 |
| 设置 | 更新方式、脚本更新检查、白名单(排除模式)、CSP 策略等 |
几个必须知道的操作:
- 禁用脚本:不删除,点一下脚本名字左边的勾选框即可。想临时停用某脚本,比注释掉
@match更快。 - 编辑脚本:点脚本右侧的
⋮→ 编辑。新建时选「添加新脚本...」。 - 权限管理:在设置里开「开发者模式」后,脚本列表会出现更多按钮,包括**「权限」**,可以单独看/改某个脚本申请了哪些站点权限。
- 排除模式(黑名单):
设置 → 实用工具 → 排除模式打开后,浏览任意网站直接被拦截并自动禁用所有脚本。排查「脚本是不是我写的锅」时非常好用。
3. 第一个脚本
新建脚本,删掉模板里全部内容,粘贴:
// ==UserScript==
// @name 我的第一个油猴脚本
// @namespace https://example.com
// @version 1.0.0
// @description 打开页面后打个招呼
// @match *://*/*
// @grant none
// ==/UserScript==
console.log('[油猴] 脚本已运行,当前页面:', document.title);
alert('你好,油猴!当前页面标题是:' + document.title);
保存(Ctrl + S),然后刷新目标页面(已打开的页面不会自动补跑脚本),就能看到效果。
想验证效果又不想弹窗?把
alert(...)换成document.title = '已被油猴改过',看标签页标题变没变即可。
元数据块里必须写的几项
| 字段 | 作用 | 建议 |
|---|---|---|
@name | 脚本名,列表里显示的名字 | 必填 |
@namespace | 命名空间,用来避免和别人的脚本重名 | 必填(随便写个网址) |
@version | 版本号 | 必填,发布后要靠它更新 |
@description | 描述 | 必填 |
@match | 生效的页面 | 必填,至少一条 |
@grant | 申请哪些权限 | 必填,不写一般等于没权限 |
@author | 作者 | 建议 |
@icon / @namespace | 图标、主页 | 建议 |
4. 元数据块(最核心的部分)
元数据块就是开头那段 // ==UserScript== 到 // ==/UserScript== 的注释。它必须在文件最顶部,且不能有多余空行(第一行就可以是它)。
常用字段全表:
// ==UserScript==
// @name 脚本名
// @namespace https://example.com
// @version 1.0.0
// @description 一句话说明这个脚本干什么
// @author 你的名字
// @icon https://example.com/icon.png
// @homepageURL https://example.com
// @supportURL https://example.com/issues
// @license MIT
// —— 匹配范围 ——
// @match https://*.example.com/*
// @include https://example.com/*
// @exclude https://example.com/login*
// @exclude-match *://*/*.png
// @noframes 不在 iframe 里重复运行
// —— 执行时机 ——
@run-at document-end
// —— 权限与外部依赖 ——
@grant GM_setValue
@grant GM_getValue
@require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
@resource style https://cdn.jsdelivr.net/npm/xui/dist/xui.css
@connect api.example.com
@connect *
@downloadURL https://raw.githubusercontent.com/you/repo/main/tampermonkey.user.js
@updateURL https://raw.githubusercontent.com/you/repo/main/tampermonkey.user.js
// ==/UserScript==
几个必须知道的重要规则
@grant写一个就等于「隔离沙箱」。只要写了任意一条@grant(哪怕是@grant none之外的东西),脚本就运行在油猴的沙箱里,此时脚本里的window不是页面的window,页面上的 JS 变量(Vue、React、jQuery)全都拿不到。@grant none表示「不要沙箱」,脚本直接在页面上下文里跑,能访问页面的一切,但不能使用任何GM_*API。二选一,不能兼得(跨域请求需要GM_xmlhttpRequest,那就必须有沙箱)。@require的代码也会在沙箱里执行,它不是往页面里塞代码。想往页面里塞代码要用别的办法(见进阶篇)。@updateURL/@downloadURL的 URL 必须以/或*.user.js结尾,否则油猴会拒绝更新。- 元数据块支持条件注释,不同浏览器 / 不同网址可以走不同分支:
// @match *://*/*
// @include https://*.taobao.com/*
// ==/UserScript==
// @if chrome
// ==/UserScript==
// @if !chrome
// ==/UserScript==
// @if /^https?:\/\/www\.example\.com/
// ==/UserScript==
5. 匹配规则:决定脚本在哪些页面运行
@match:官方标准写法(推荐)
语法:@match <scheme>://<host><path>
| 部分 | 可写值 |
|---|---|
scheme | http、https、* |
host | example.com、*.example.com、* |
path | 路径,必须以 / 开头 |
常见例子:
// @match *://*/* 所有网站(http + https)
// @match https://example.com/* 某个站的所有页面
// @match https://*.example.com/* example.com 及其所有子域
// @match https://www.example.com/a/b/* 指定路径
*.example.com能匹配example.com本身和任意层级子域。path里只有最后一个*有效,且*不能出现在 host 中间(a*.com是非法的)。- 只能用
http/https,不能写file://、chrome-extension://。
@include:老式写法,更灵活
支持任意位置的通配符,还能匹配其他协议:
// @include https://example.com/*
// @include *example.com*
// @include https://www.example.com/path/*
// @include file:///D:/local/*.html
实际写脚本时优先用 @match,因为它语法更严格、不容易写错,而且 Chrome 扩展规范要求必须用 @match 或 @include 至少给一条。
排除规则
// @exclude https://example.com/login* 排除时,@include/@match 两种都认
// @exclude-match *://*/*.pdf 排除时,只认 @match 语法
怎么快速得到正确的匹配规则
不用手写。装好脚本后:
- 点扩展图标 → 实用工具(或直接进管理页的实用工具页)。
- 找到 「在站点上显示」/「安装用户脚本」 相关区域,或者在你要处理的网页上打开管理页。
- 很多版本会在面板里直接给出「安装到此站点」的按钮,生成的
@match一定是能用的。
手写的话,打开目标页面,在控制台执行 location.href 拼一下就行。
6. @run-at:决定脚本什么时候执行
@run-at document-start
@run-at document-body
@run-at document-end
@run-at document-idle
@run-at context-menu
| 值 | 时机 | 典型用途 |
|---|---|---|
document-start | 页面脚本还没执行,DOM 只有 <html> | 劫持 window.X、hook fetch / XMLHttpRequest、防 XSS |
document-body | <body> 一出现 | 需要尽早但又要操作 DOM |
document-end | DOM 解析完成(等价于 DOMContentLoaded 前后) | 最常用,改样式、读数据、加按钮 |
document-idle | 页面加载完后再等一小会儿 | 等框架渲染完(Vue / React 异步挂载) |
context-menu | 只在浏览器右键菜单里生效 | 自定义右键菜单项 |
默认值是 document-idle,也就是「等页面基本加载完」。绝大多数脚本不需要写这一行。
⚠️
document-start阶段document.body还是null,直接document.querySelector什么都找不到。要操作 DOM 就要等document-end,或者自己监听DOMContentLoaded。
7. @grant:决定能不能用 GM API
// @grant none
| 写法 | 效果 |
|---|---|
不写 @grant | 视版本而定,多数版本按「无沙箱」处理,行为不稳定,别这么写 |
@grant none | 无沙箱,脚本与页面共用 window,可用页面的一切,但所有 GM_* API 都不可用 |
@grant GM_xxx(逐条列出) | 有沙箱,脚本里的 window 与页面隔离,只能用它列出来的 GM_* API |
沙箱模式下,想访问页面自己的东西要用 unsafeWindow:
// @grant none
console.log('无沙箱:window === unsafeWindow ?', window === window.unsafeWindow);
// @grant GM_setValue
console.log('有沙箱:window === unsafeWindow ?', window === unsafeWindow); // false
console.log('页面里的变量要通过 unsafeWindow 拿:', unsafeWindow.jQuery);
选了哪些 @grant,油猴就会只把这些权限提示给你(设置里也能逐条核对)。需要什么申请什么,别一口气全勾。
8. 常用 GM API 入门
8.1 存储:GM_setValue / GM_getValue
最常用的两个 API,用来把数据存在油猴里(跨网站共享、永久保存、不会被清缓存清掉)。
// 保存
GM_setValue('count', 1);
GM_setValue('config', { theme: 'dark', auto: true }); // 对象直接存,不用自己序列化
// 读取
const count = GM_getValue('count', 0); // 第二个参数是默认值
const config = GM_getValue('config', {});
if (config.theme !== 'dark') GM_setValue('config', { ...config, theme: 'dark' });
// 删除
GM_deleteValue('count');
// 全部键
GM_listValues();
值类型是按结构保存的,存进去的对象读出来还是对象,不用 JSON.parse。
同步还是异步?
GM_setValue/GM_getValue/GM_deleteValue/GM_listValues:同步,马上能拿到返回值。GM.setValue/GM.getValue(新式GM对象):返回 Promise,const v = await GM.getValue('k')。- 新式 API 需要用
@grant GM.setValue这样的写法授权。
⚠️ 油猴也有配额上限(默认每个脚本 1MB 左右),存大图 / 大 JSON 请慎用。
8.2 样式:GM_addStyle
GM_addStyle(`
.ad-banner { display: none !important; }
.price { color: #ff3b30 !important; font-weight: 700; }
`);
// 也支持传 CSS 文本变量
const css = 'body { background: #111; }';
GM_addStyle(css);
8.3 弹窗:alert / confirm / prompt
const ok = confirm('确定要删除吗?');
const name = prompt('请输入新名字', '默认值');
if (name) {
GM_setValue('name', name);
alert('已保存:' + GM_getValue('name'));
}
8.4 剪贴板:GM_setClipboard
// 传统 API:同步
GM_setClipboard('要复制的文本', 'text');
GM_setClipboard('<b>HTML</b>', 'html'); // 可指定 text / html
// 新式:返回 Promise
await GM.setClipboard('异步版复制');
普通 navigator.clipboard 也可用,但在非 https 页面会被浏览器拒绝,用 GM_setClipboard 更稳。
8.5 打开新标签:GM_openInTab
GM_openInTab('https://example.com'); // 前台打开
GM_openInTab('https://example.com', true); // 后台打开,不抢焦点
GM_openInTab('https://example.com', { active: false, insert: true, setParent: true });
GM_openInTab('https://example.com', { container: 'browser' }); // 用浏览器标签页而不是默认应用
8.6 下载文件:GM_download
GM_download({
url: 'https://example.com/file.zip',
name: 'myfile.zip', // 保存的文件名
onload: () => console.log('下载完成'),
onerror: (e) => console.error('下载失败', e),
ontimeout: () => console.log('下载超时'),
});
老写法(不需要对象参数)也兼容:GM_download('https://example.com/a.txt', 'a.txt')。
8.7 右键菜单:GM_registerMenuCommand
脚本会在油猴面板和页面右键菜单里多出一个菜单项:
GM_registerMenuCommand('刷新数据', () => {
location.reload();
});
GM_registerMenuCommand('导出配置', () => {
const text = JSON.stringify(GM_getValue('config', {}), null, 2);
GM_setClipboard(text);
alert('配置已复制到剪贴板');
});
// 第三个参数可以传图标地址
GM_registerMenuCommand('带图标项', () => alert('hi'), 'https://example.com/i.png');
这个功能非常好用——把一些不方便自动执行的维护操作(重置配置、清缓存、导出日志)挂到菜单里。
8.8 通知:GM_notification
GM_notification({
title: '脚本提醒',
text: '这个价格已经低于你设定的阈值了',
image: 'https://example.com/icon.png',
timeout: 5000, // 毫秒
onclick: () => window.focus(),
ondone: () => console.log('通知关闭'),
});
// 简化写法
GM_notification('纯文本提醒');
8.9 元素:GM_addElement
往页面里塞一个自己的标签(比如绕过某些 CSS 限制):
const el = GM_addElement('my-script-tag', { textContent: '我是插入的自定义元素' });
el.style.cssText = 'position:fixed;bottom:10px;right:10px;z-index:99999;background:#000;color:#fff;padding:6px;border-radius:6px;';
el.addEventListener('click', () => location.reload());
el.remove(); // 移除
9. 跨域请求 GM_xmlhttpRequest
这是油猴最有价值的能力之一:它不受浏览器同源策略限制,也不受 CORS 预检影响,可以直接请求任意接口。
// @grant GM_xmlhttpRequest
// @connect api.example.com
GM_xmlhttpRequest({
url: 'https://api.example.com/data',
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + GM_getValue('token', ''),
},
data: JSON.stringify({ page: 1, size: 20 }), // 发送的请求体
timeout: 10000, // 超时毫秒
responseType: 'json', // text / json / blob / arraybuffer / document
onload: (res) => {
console.log('状态码:', res.status);
console.log('响应头:', res.responseHeaders);
console.log('响应体:', res.response); // responseType 为 json 时直接是对象
GM_setValue('lastData', res.response);
},
onerror: (e) => console.error('请求失败(网络错误)', e),
ontimeout: () => console.error('请求超时'),
onabort: () => console.log('请求被中止'),
});
关键点
@connect必须写,否则GM_xmlhttpRequest调用会直接失败。写*表示允许所有域名(权限提示会很吓人,谨慎使用)。- 需要
onload/onerror/ontimeout/onabort至少一个,否则这个请求没有回调,等于白跑。 responseType: 'json'时res.response已经是对象;不写则是字符串,需要自己JSON.parse。GET请求的参数放url上,POST的参数放data。- 新式写法:
GM.xmlhttpRequest(),返回 Promise,风格更现代,但需@grant GM.xmlhttpRequest。
新式 Promise 写法
// @grant GM.xmlhttpRequest
const res = await GM.xmlhttpRequest('https://api.example.com/data');
console.log(res.response);
10. 引入外部库 @require / @resource
@require:把库代码引入沙箱
// @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/axios@1.6.7/dist/axios.min.js
之后直接用,不要加 name.module.exports 之类:
// @grant none
// @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
console.log($('#someId').text());
| 注意 | 说明 |
|---|---|
| 锁版本 | 一定写死版本号,别用 @latest,作者一改代码你的脚本就炸 |
| 用国内可访问的 CDN | cdn.jsdelivr.net、unpkg.com、cdnjs.cloudflare.com 都可以;国内网络可考虑 fastly.jsdelivr.net |
@require 也在沙箱 | 库是被注入到沙箱的,不是往页面里塞代码 |
| 只在需要时引 | 库很大(如 moment.js)会拖慢页面首屏,能不引就不引 |
@resource:存 CSS / 图片 / 文本,不执行
// @resource 主题样式 https://cdn.jsdelivr.net/npm/xui@1.0.0/dist/xui.css
// @resource 图标 https://example.com/logo.png
使用:
const css = GM_getResourceText('主题样式'); // 取文本内容
GM_addStyle(css); // 文本型资源最常见的用法
const url = GM_getResourceURL('图标'); // 取可直接赋给 src 的 URL
document.querySelector('#logo').src = url;
const blob = GM_getResourceURL('图标', true); // true = 转成 blob: URL
资源只在脚本首次安装/更新时下载一次,之后从本地读,断网也能用。
11. 注入 CSS:GM_addStyle
除了 GM_addStyle,还有更彻底的做法:把样式直接写进 <style> 标签,这样在无沙箱模式下也能用,而且能处理页面自身的样式表:
// 无沙箱时
const style = document.createElement('style');
style.textContent = `
.theater-container { display: none !important; }
a[href*="ad"] { display: none !important; }
`;
document.head.appendChild(style);
两种方式的选择:
| 场景 | 用 |
|---|---|
| 有沙箱,想少操心 | GM_addStyle |
无沙箱 / 想用 !important 覆盖第三方样式 | 手写 <style> 标签 |
| 想要「用户开关」 | 把 CSS 存进 GM_setValue,动态切换 |
用户开关的写法很实用:
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
const DISABLED = 'my_script_disabled';
GM_registerMenuCommand('关闭/开启本脚本', () => {
GM_setValue(DISABLED, !GM_getValue(DISABLED, false));
location.reload();
});
if (GM_getValue(DISABLED, false)) {
console.log('脚本已停用');
} else {
GM_addStyle('.ad { display: none !important; }');
}
12. 本地调试与常见问题
怎么调试
- 看日志:在网页按
F12打开控制台,油猴脚本的console.log输出在这里([油猴]之类的标记可用来区分来源)。 - 看脚本是否注入:
view-source:看不到用户脚本;正确做法是在控制台执行typeof unsafeWindow === 'undefined',或直接看脚本有没有打印日志。 - 加保护:
console.log之前先判断页面元素是否存在,避免整段脚本因一行报错全部失效:
if (!document.querySelector('.main-container')) {
console.warn('[我的脚本] 页面结构可能已变化,脚本终止');
return; // 注意:只在函数里用
}
脚本不生效的排查顺序
| 顺序 | 检查项 |
|---|---|
| 1 | Chrome/Edge 的「允许用户脚本」开关打开了吗?(最高频原因) |
| 2 | 脚本左侧的启用勾选是打开的吗? |
| 3 | @match / @include 能不能匹配当前网址? |
| 4 | 刷新过页面吗?(老页面不会自动补跑) |
| 5 | 元数据块是不是在第一行、结尾有没有多余空行或 ==/UserScript== 拼错? |
| 6 | 打开「实用工具 → 排除模式」确认自己没把站点排除掉 |
| 7 | 页面控制台有没有红色报错?多半是选择器失效或沙箱访问不到页面变量 |
| 8 | 是否撞了同名脚本?用 @namespace 区分 |
与本项目(DrissionPage / ruyipage)的关系
油猴脚本和浏览器自动化脚本解决的是同一个问题的两端:
| 油猴用户脚本 | DrissionPage / ruyipage 自动化 | |
|---|---|---|
| 运行环境 | 浏览器里,人在操作页面时执行 | Python 进程里,程序自己驱动浏览器 |
| 适合 | 个人长期使用、页面上的即时增强 | 批量任务、无人值守、数据采集 |
| 能否互相配合 | 可以:Python 控制浏览器打开带油猴的页面,页面加载后油猴脚本自动执行 | 同左 |
所以在自动化里写逻辑,优先放 Python 侧;只有在「需要人手动访问、长期复用」的场景才写成油猴脚本。
下一步
- 进阶篇:Tampermonkey 进阶 —— 沙箱穿透、hook
fetch/XHR、SPA 路由监听、拦截接口数据、完整 API 清单、脚本发布与自动更新。 - 去 Greasy Fork 找现成脚本,右侧能直接看别人怎么写元数据块。
- 官方文档:Tampermonkey 文档