油猴脚本学习1——元数据头部
元数据脚本基本结构就是
基本的脚本就是这样的
// ==UserScript==
// @name 我的第一个脚本
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 修改网页背景色
// @author You
// @match https://*/*
// @grant none
// ==/UserScript==(function() {
'use strict';
document.body.style.backgroundColor = '#f0f8ff';
})();
// ==UserScript==
// @key value
// @key value
// ==/UserScript==
然后中间填写
// @name My Awesome Script
// @name 百度搜索优化,
名字空间,为脚本命名,命名主要是为了避免相互冲突
想象一下这个场景:
你安装了两个脚本,脚本A和脚本B。
脚本A定义了一个全局变量
var config = { ... }。脚本B也定义了一个同名的全局变量
var config = { ... }。当两个脚本都在同一个页面上运行时,后加载的脚本会覆盖先加载的脚本的变量。这会导致脚本A无法正常工作,因为它的
config被篡改了。命名空间就是为了解决这个问题而生的。
2. 工作原理
当你在脚本的元数据(
@namespace)中声明一个命名空间后,油猴会将你的脚本包裹在一个“沙箱”或“隔离环境”中。你脚本中声明的所有变量和函数,默认都不会泄露到全局window对象上。但是,如果你必须要设置一个全局变量(例如某个网站的特殊API要求),命名空间就为这个全局变量提供了一个“前缀”或“所属范围”,极大地降低了冲突的概率。
// @version 1.0
// @version 2.1.5
// @version 0.8.0-beta
版本号
油猴脚本中版本号的核心作用
1. 触发更新检测 - 最核心的功能
这是版本号在油猴中存在的首要理由。
油猴管理器会定期(例如每天)检查你安装的脚本是否有更新。
检查的方式就是对比脚本元数据中的
@version和脚本发布网址上的@version。如果发布网址上的版本号高于你本地安装的版本号,油猴就会在管理面板中显示一个更新按钮(通常带有数字徽标),提示你有新版本可用。
没有版本号,这个自动更新机制就完全失效了。
2. 管理脚本的更新流程
当用户点击更新时,油猴会:
根据版本号判断有更新。
显示一个对比界面,让你查看新版本修改了哪些代码和元数据。
允许你选择“立即安装”或“忽略此次更新”。
版本号是这个流程的唯一凭证。
3. 向用户传达变更的严重性
油猴社区通常遵循 语义化版本 规范,用户可以通过版本号的变化快速了解这次更新的性质:
| 版本号变化 | 含义 | 用户应有的预期 |
|---|---|---|
| 主版本号 变化 (例如 2.5.0 -> 3.0.0) | 重大更新 | 脚本可能进行了彻底重写,可能与网站新布局适配,但可能与你用的其他脚本冲突,或设置项被重置。需要谨慎更新。 |
| 次版本号 变化 (例如 2.5.0 -> 2.6.0) | 新增功能 | 加入了新功能,但不会破坏现有功能。通常是安全且值得更新的。 |
| 修订号 变化 (例如 2.5.0 -> 2.5.1) | 问题修复 | 修复了一些Bug,没有新功能。强烈建议更新。 |
4. 脚本仓库的强制要求
在 GreasyFork、OpenUserJS 等主流的油猴脚本发布平台上:
@version是必须的元数据,没有它你无法成功发布脚本。每次更新脚本时,都必须提高版本号,平台不允许你上传一个版本号与现有版本相同或更低的脚本。
描述
// @description 增强网页功能
// @description 自动填充表单,移除广告
作者
// @author Your Name
// @author Anonymous
3. 匹配规则字段
// @match *://*.example.com/*
// @match https://www.google.com/search*
// @match http://localhost:8080/app/*
@include - 宽松匹配
javascript
// @include http://* // @include https://* // @include *://*.example.com/path/*
@exclude - 排除URL
javascript
// @exclude *://*.example.com/admin/* // @exclude https://www.google.com/search?q=test*
运行时
// @run-at document-start // 尽早执行
// @run-at document-body // body元素出现时
// @run-at document-end // DOMContentLoaded时
// @run-at document-idle // 默认,页面加载完成后
// @run-at context-menu // 右键菜单点击时
1.
// @run-at document-start执行时机:资源开始加载,但尚未解析 DOM。
特点:
脚本在页面 HTML 刚开始下载时就注入
document.readyState === "loading"DOM 树尚未构建,
document.body为null可以拦截和修改最初的 HTML 内容
// ==UserScript==
// @run-at document-start
// ==/UserScript==// 拦截和修改原始 HTML
document.addEventListener('beforescriptexecute', function(e) {// 阻止某些脚本执行if (e.target.src.includes('advertisement')) {e.preventDefault();}
});// 修改尚未解析的页面内容
document.documentElement.addEventListener('DOMNodeInserted', function(e) {// 监控 DOM 节点的插入
});2.
// @run-at document-body执行时机:
<body>元素首次出现时。
特点:
当
document.body不为null但可能为空时执行DOM 刚开始构建,页面内容可能还不完整
比
document-start稍晚,比document-end早典型用途:
// ==UserScript== // @run-at document-body // ==/UserScript==// 立即在 body 中添加元素 const loader = document.createElement('div'); loader.id = 'my-custom-loader'; document.body.appendChild(loader);// 设置初始样式或布局 document.body.style.overflow = 'hidden'; // 先隐藏滚动条
3.
// @run-at document-end执行时机:DOM 解析完成时(
DOMContentLoaded事件)。
特点:
DOM 树已完全构建,可以安全地操作页面元素
图片等资源可能仍在加载
document.readyState === "interactive"这是最常用的时机之一
// ==UserScript== // @run-at document-end // ==/UserScript==// 安全地操作页面元素 const buttons = document.querySelectorAll('button'); buttons.forEach(btn => {btn.style.backgroundColor = 'red'; });// 修改页面内容 const header = document.querySelector('h1'); if (header) {header.textContent = '已修改的标题'; }
4.
// @run-at document-idle执行时机:页面完全加载完成后(
window.onload事件)。
特点:
默认值,如果不指定
@run-at则使用此选项所有资源(图片、样式表等)都已加载完成
document.readyState === "complete"确保页面完全稳定后再执行脚本
// ==UserScript== // @run-at document-idle // ==/UserScript==// 需要页面完全加载后才能执行的操作 const allImages = document.images; console.log('页面共有图片:', allImages.length);// 进行复杂的 DOM 操作 setTimeout(() => {// 确保所有动态内容都已加载processDynamicContent(); }, 1000);5.
// @run-at context-menu执行时机:用户在页面上右键点击脚本名称时。
特点:
脚本不会自动执行
只在用户通过右键菜单选择时才运行
适合工具类、辅助类脚本
// ==UserScript== // @run-at context-menu // ==/UserScript==// 右键菜单工具脚本 const selectedText = window.getSelection().toString().trim();if (selectedText) {// 处理选中的文本console.log('选中的文本:', selectedText);// 可以翻译、搜索、格式化等window.open(`https://www.google.com/search?q=${encodeURIComponent(selectedText)}`); } else {alert('请先选择一些文本'); }执行时机对比总结
指令 执行时机 DOM 状态 资源加载 使用频率 document-start最早 未解析 刚开始 中等 document-body较早 body 刚出现 进行中 较少 document-endDOM 解析完成 DOM 就绪 进行中 很高 document-idle页面完全加载 完全就绪 已完成 最高(默认) context-menu用户触发 任意时机 任意状态 特殊用途
实际选择建议
需要拦截修改原始内容 →
document-start操作页面主要元素 →
document-end(最常用)依赖完整页面资源 →
document-idle(默认)工具类、按需使用 →
context-menu特定布局调整 →
document-body
@grant - 权限控制
// @grant none // 不使用特殊API
// @grant GM_setValue // 存储数据
// @grant GM_getValue // 读取数据
// @grant GM_xmlhttpRequest // 跨域请求
// @grant GM_addStyle // 添加CSS
// @grant GM_notification // 桌面通知
// @grant GM_setClipboard // 剪贴板操作
// @grant unsafeWindow // 访问页面window对象
1. // @grant none
含义:脚本运行在页面的原生环境中,不使用任何油猴特殊 API。
特点:
脚本与页面共享同一个 JavaScript 环境
可以直接访问页面的
window对象权限最低,但执行效率最高
无法使用油猴的存储、网络请求等高级功能
使用场景:
// ==UserScript== // @grant none // ==/UserScript==// 可以直接操作页面变量 const pageVariable = window.somePageVariable;// 可以直接调用页面函数 if (typeof pageFunction === 'function') {pageFunction(); }// 简单的 DOM 操作 document.querySelector('button').click();2.
// @grant GM_setValue和// @grant GM_getValue含义:启用键值对存储功能,用于持久化保存数据。
特点:
数据在浏览器关闭后依然保存
每个脚本有独立的存储空间
适合保存用户设置、历史记录等
// ==UserScript== // @grant GM_setValue // @grant GM_getValue // ==/UserScript==// 保存用户设置 GM_setValue('theme', 'dark'); GM_setValue('lastVisit', new Date().toISOString());// 读取设置 const theme = GM_getValue('theme', 'light'); // 第二个参数是默认值 const lastVisit = GM_getValue('lastVisit');console.log(`当前主题:${theme},最后访问:${lastVisit}`);3.
// @grant GM_xmlhttpRequest含义:启用跨域网络请求功能。
特点:
可以绕过浏览器的同源策略
支持所有 HTTP 方法(GET、POST 等)
可以自定义请求头、超时时间等
// ==UserScript== // @grant GM_xmlhttpRequest // ==/UserScript==// 调用外部 API GM_xmlhttpRequest({method: 'GET',url: 'https://api.example.com/data',headers: {'User-Agent': 'MyUserScript'},onload: function(response) {const data = JSON.parse(response.responseText);console.log('获取到的数据:', data);},onerror: function(error) {console.error('请求失败:', error);} });4.
// @grant GM_addStyle含义:向页面注入 CSS 样式。
特点:
安全地添加样式,避免与页面样式冲突
自动处理样式作用域
适合修改页面外观
// ==UserScript== // @grant GM_addStyle // ==/UserScript==// 添加自定义样式 GM_addStyle(`.my-custom-widget {position: fixed;top: 10px;right: 10px;background: #fff;border: 1px solid #ccc;z-index: 9999;}/* 隐藏广告 */.advertisement {display: none !important;}/* 修改页面主题 */body {background-color: #f0f0f0 !important;} `);5.
// @grant GM_notification含义:显示桌面通知。
特点:
即使用户不在当前标签页也能看到
适合重要事件提醒
使用场景:
// ==UserScript== // @grant GM_notification // ==/UserScript==// 显示通知 GM_notification({title: '任务完成',text: '数据已成功处理',image: 'https://example.com/icon.png',timeout: 3000, // 3秒后自动关闭onclick: function() {// 点击通知时的回调window.focus();} });6.
// @grant GM_setClipboard含义:操作系统剪贴板。
特点:
可以安全地复制文本到剪贴板
不需要用户手动复制
使用场景:
// ==UserScript== // @grant GM_setClipboard // ==/UserScript==// 复制文本到剪贴板 function copyToClipboard(text) {GM_setClipboard(text, 'text'); // 第二个参数是数据类型GM_notification({title: '复制成功',text: '内容已复制到剪贴板'}); }// 使用示例 copyToClipboard('Hello, World!');7.
// @grant unsafeWindow含义:直接访问页面的原始 window 对象。
特点:
绕过油猴的安全沙箱
有风险,可能被页面代码检测或干扰
仅在必要时使用
使用场景:
// ==UserScript== // @grant unsafeWindow // ==/UserScript==// 访问页面全局变量 const pageConfig = unsafeWindow.pageConfig;// 修改页面全局变量(谨慎使用!) unsafeWindow.someGlobalFunction = function() {console.log('被脚本修改的函数'); };// 与页面代码交互 if (unsafeWindow.jQuery) {unsafeWindow.jQuery('body').css('background-color', 'red'); }
Grant 指令 功能 风险等级 使用频率 none无特殊权限 低 高 GM_setValue/GM_getValue数据存储 低 高 GM_xmlhttpRequest跨域请求 中 高 GM_addStyle添加样式 低 高 GM_notification桌面通知 低 中 GM_setClipboard剪贴板操作 中 中 unsafeWindow访问页面对象 高 低
最佳实践建议
按需申请权限:只申请脚本真正需要的权限
优先使用安全API:避免使用
unsafeWindow,除非必要组合使用权限:可以同时申请多个权限
javascript
复制下载// ==UserScript== // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_addStyle // ==/UserScript==测试权限可用性:在使用前检查 API 是否可用
javascript
复制下载if (typeof GM_setValue !== 'undefined') {GM_setValue('key', 'value'); }正确使用
@grant指令可以让你的脚本既功能强大又安全可靠。
// ==UserScript==
// @require https://code.jquery.com/jquery-3.6.0.min.js // 先加载
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js // 后加载
// @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js // 最后加载
// ==/UserScript==
规则:@require 按声明顺序同步加载和执行。
执行时机
在所有
@require加载并执行完成后,才执行脚本主代码执行时机受
@run-at指令影响
实际应用场景
场景 1:使用 UI 库创建界面
// ==UserScript==
// @name 带UI的增强脚本
// @namespace http://tampermonkey.net/
// @version 1.0
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @require https://unpkg.com/sweetalert/dist/sweetalert.min.js
// @match *://example.com/*
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==(function() {'use strict';// 使用引入的库$(document).ready(function() {// 添加自定义按钮$('body').prepend('<button id="myBtn">点击我</button>');$('#myBtn').click(function() {// 使用 SweetAlert 显示美观的弹窗swal({title: "你好!",text: "这是一个使用外部库的示例",icon: "success",buttons: true,});});});
})();场景 2:工具函数库
javascript
复制
下载
// ==UserScript== // @name 数据处理脚本 // @namespace http://tampermonkey.net/ // @version 1.0 // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/axios/1.4.0/axios.min.js // @match *://data.example.com/* // @grant none // ==/UserScript==(function() {'use strict';// 数据处理const processData = (data) => {return _.chain(data).filter(item => item.active).groupBy('category').mapValues(group => _.sumBy(group, 'value')).value();};// 使用 axios 进行 HTTP 请求const fetchData = async () => {try {const response = await axios.get('/api/data');const processed = processData(response.data);console.log('处理后的数据:', processed);} catch (error) {console.error('请求失败:', error);}};fetchData(); })();
@resource - 加载资源
javascript
复制
下载
// @resource icon1 http://example.com/icon.png // @resource html http://example.com/template.html // @resource css https://example.com/styles.css
1. 加载图片资源
javascript
// @resource icon1 http://example.com/icon.png
特点:图片会被缓存,避免重复下载可以获取图片的 base64 数据 URL使用示例:javascript
// ==UserScript==
// @name 使用图片资源的脚本
// @namespace http://tampermonkey.net/
// @version 1.0
// @resource customIcon https://www.google.com/favicon.ico
// @resource banner https://via.placeholder.com/300x100.png
// @match *://*/*
// @grant GM_getResourceURL
// ==/UserScript==(function() {'use strict';// 获取图片的 Data URLconst iconUrl = GM_getResourceURL('customIcon');const bannerUrl = GM_getResourceURL('banner');// 在页面中使用图片const myIcon = document.createElement('img');myIcon.src = iconUrl;myIcon.style.width = '16px';myIcon.style.height = '16px';document.body.appendChild(myIcon);
})();2. 加载 HTML 模板
javascript
复制
下载
// @resource html http://example.com/template.html特点:
预加载 HTML 片段或模板
避免在脚本中嵌入大量 HTML 字符串
使用示例:
javascript
复制
下载
// ==UserScript== // @name 使用HTML模板的脚本 // @namespace http://tampermonkey.net/ // @version 1.0 // @resource popupTemplate https://raw.githubusercontent.com/user/repo/master/templates/popup.html // @match *://*/* // @grant GM_getResourceText // ==/UserScript==(function() {'use strict';// 获取 HTML 模板内容const templateHtml = GM_getResourceText('popupTemplate');// 插入到页面中const container = document.createElement('div');container.innerHTML = templateHtml;container.id = 'my-custom-popup';document.body.appendChild(container);// 现在可以操作模板中的元素const closeBtn = document.getElementById('close-popup');if (closeBtn) {closeBtn.addEventListener('click', function() {container.style.display = 'none';});} })();
// @resource css https://example.com/styles.css配套的 GM API 函数
1. GM_getResourceURL(资源名称)
获取资源的 Data URL(适用于图片、字体等二进制资源)。
javascript
mageUrl = GM_getResourceURL('myImage'); // 返回: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
2. GM_getResourceText(资源名称)
获取资源的文本内容(适用于 HTML、CSS、JSON 等文本资源)。
javascript
const template = GM_getResourceText('myTemplate');
// 返回: "<div class='widget'>Hello World</div>"@connect - 跨域权限
javascript
复制
下载
// @connect example.com // @connect api.github.com // @connect localhost // @connect * // 允许所有域名
6. 更新相关字段
@updateURL - 更新地址
javascript
复制
下载
// @updateURL https://example.com/myscript.meta.js
@downloadURL - 下载地址
javascript
复制
下载
// @downloadURL https://example.com/myscript.user.js
@supportURL - 支持页面
javascript
复制
下载
// @supportURL https://github.com/user/repo/issues
7. 高级功能字段
@noframes - 禁止在iframe运行
javascript
复制
下载
// @noframes
@unwrap - 不包装脚本
javascript
复制
下载
// @unwrap
@nocompat - 兼容性控制
javascript
复制
下载
// @nocompat Chrome 90+ // @nocompat Firefox 88+
