Skip to main content

后台脚本

后台脚本适用于持续运行类型的脚本。后台脚本是脚本猫特有的脚本,后台脚本运行在沙盒中,无法操作 DOM 对象。可使用与油猴一致的 GM API 进行开发,对于兼容性会在文档中标出。

后台脚本 (@background)

后台脚本由 @background 属性声明,后台脚本将允许在开启脚本或者浏览器启动后,让脚本在后台持续运行。

定时脚本(@crontab

定时脚本属于后台脚本的一种,适用于按时间周期重复执行的任务。

定时脚本通过 @crontab 属性声明,支持分钟级与秒级调度,并提供脚本猫扩展语法 once,用于避免在同一时间周期内重复执行。

⚠️ 注意事项:

  • 一个脚本中 只有第一个 @crontab 会生效
  • 建议脚本的单次执行时间 + 重试时间不要大于 cron 间隔,否则可能发生执行重叠

Cron 表达式说明

脚本猫的 cron 基于 node-cron 实现,在标准 cron 语法上做了少量扩展。

表达式格式

标准 5 位格式(推荐)

分 时 日 月 周

扩展 6 位格式(不推荐)

秒 分 时 日 月 周

⚠️ 不推荐使用 6 位格式 浏览器环境无法保证秒级精准执行,并且会增加性能开销,后台页可能被延迟调度。

各字段可用语法

语法含义示例
*任意值*(每分钟 / 每小时)
数字指定值5(第 5 分)
a,b,c多个离散值1,15,30
a-b连续区间10-23
*/n每 n 执行一次*/5
a-b/n区间步长10-50/10

周(day of week)规则

  • 1–6:周一 ~ 周六
  • 07:周日

once 扩展语法说明

once 的含义

在 cron 表达式中使用 once,表示:

在当前时间周期内,只允许成功执行一次

即使在同一周期内,后续时间点仍然符合 cron 规则,也不会再次执行。

once 所在位置 = 限制的时间周期

once 写在哪一位,就表示“在该时间粒度内只执行一次”。

once 位置行为含义
分位每分钟只执行一次
时位每小时只执行一次
日位每天只执行一次
月位每月只执行一次
周位每周只执行一次

示例:

* once * * *   // 每小时只执行一次
* * once * * // 每天只执行一次

once 与范围 / 列表 / 步长的组合行为

once 可以与任何 cron 语法组合使用,但规则只有一条:

在同一周期内,只要成功执行过一次,其余命中的时间点都会被忽略

示例一:范围

* 10 once * *

含义:

  • 每天 10:00–10:59 都是候选时间
  • 当天第一次命中后
  • 10:05–10:59 将不再执行

示例二:列表

* 1,3,5 once * *

含义:

  • 每天 1 点、3 点、5 点是候选时间
  • 若 1 点已执行
  • 3 点、5 点将被忽略

示例三:步长

* */4 once * *

含义:

  • 每天 0、4、8、12、16、20 点是候选时间
  • 当天第一次执行后
  • 其余时间点将不再执行

@crontab 示例

常见

//@crontab * * * * *        // 每分钟执行一次
//@crontab * * * * * * // 每秒执行一次(不推荐)
//@crontab 0 */6 * * * // 每 6 小时整点执行
//@crontab 15 */6 * * * // 每 6 小时的第 15 分执行
//@crontab * once * * * // 每小时最多执行一次
//@crontab * * once * * // 每天最多执行一次
//@crontab * 10 once * * // 每天 10 点时段内只执行一次 (假设当10:04时运行了一次,10:05-10:59的后续的时间将不会再运行)
//@crontab * */4 once * * // 每天每 4 小时检查一次(最多一次) (假设当4点时运行了一次,8,12,16,20,24点等后续的时间将不会再运行)

进阶

//@crontab * 1,3,5 once * *    // 每天1点3点5点中运行一次(假设当1点时运行了一次,3,5点将不会再运行)
//@crontab * 10-23 once * * // 每天10点-23:59中运行一次(假设当10:04时运行了一次,10:05-23:59的后续时间将不会再运行)
//@crontab * once 13 * * // 每个月的13号的每小时运行一次

使用建议

适合使用 once 的场景

  • 每天 / 每小时 只需要执行一次 的任务

  • 状态检查、同步、上报类脚本

  • 避免以下问题:

    • 浏览器长时间未打开
    • 后台页调度延迟
    • 浏览器重启导致的重复执行

不建议使用 once 的场景

  • 必须精确在某一时刻执行的任务
  • 执行时间可能明显超过 cron 间隔的脚本
  • 对执行次数有强一致性要求的任务

Cron 表达式测试

测试 cron 表达式时,请将 once 临时替换为 *。 注意测试工具有可能不支持 扩展 6 位格式。

推荐工具:

在脚本列表页面中,将鼠标放在 运行状态栏 上,可查看脚本的下一次执行时间

日志

在脚本列表页面,鼠标放置在 运行状态栏 上,会提示脚本的运行状态, 点击后可弹出通过 GM_log 打印的日志内容。

脚本调试

后台脚本可以直接在脚本编辑页面进行调试,但存在以下问题:

  • value 无法正常同步
  • registerMenu 菜单无法正常触发

如果需要调试真实运行环境,请前往扩展设置中开启 开发人员模式, 然后打开扩展的 background.html 页面进行调试。

运行时产生的错误也可以在运行日志中查看。

image-20210903144155450

Promise

十分推荐以下写法,这样也便于脚本管理器进行脚本监控。 如果脚本中包含异步操作,必须返回 Promise

// ==UserScript==
// @name 后台脚本
// @namespace wyz
// @version 1.0.0
// @author wyz
// @background
// ==/UserScript==
return new Promise((resolve, reject) => {
if (Math.round((Math.random() * 10) % 2)) {
resolve("ok"); // 执行成功
} else {
reject("error"); // 执行失败,并返回错误原因
}
});
// ==UserScript==
// @name 每天运行一次的定时脚本
// @namespace wyz
// @version 1.0.0
// @author wyz
// @crontab * * once * *
// ==/UserScript==
return new Promise((resolve, reject) => {
if (Math.round((Math.random() * 10) % 2)) {
resolve("ok"); // 执行成功
} else {
reject("error"); // 执行失败,并返回错误原因
}
});
// ==UserScript==
// @name 请求API
// @namespace wyz
// @version 1.0.0
// @author wyz
// @crontab * * once * *
// ==/UserScript==
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url: "https://bbs.tampermonkey.net.cn/",
onload() {
resolve("ok"); // 执行成功
},
onerror() {
reject("error"); // 执行失败,并返回错误原因
},
});
});

请注意将 resolve / reject 放在脚本逻辑真正结束之后。 一旦调用,管理器会认为脚本已经执行完毕,后续的 GM 操作将不会生效。

错误重试

脚本猫后台脚本支持错误重试。 当脚本执行失败时,可以 reject 一个 CATRetryError 来触发重试。

  • 最小重试时间:5 秒
  • 请避免与脚本本身的执行时间冲突,否则可能导致重复执行
// ==UserScript==
// @name 重试示例
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description try to take over the world!
// @author You
// @crontab * * once * *
// @grant GM_notification
// ==/UserScript==

return new Promise((resolve, reject) => {
GM_notification({
title: "retry",
text: "10秒后重试",
});
reject(new CATRetryError("xxx错误", 10));
});