油猴脚本开发实战:原理、避坑与智慧树自动答题
2026/9/19 7:27:57 网站建设 项目流程

1. 项目本质与真实能力边界:别被标题带偏,先搞清油猴能做什么、不能做什么

“油猴(Tampermonkey)插件——免费各平台会员视频、自动刷题等需求”,这个标题在社交平台和资源站上非常典型,它用几个高吸引力关键词组合在一起,制造出一种“一装即爽、无所不能”的错觉。但作为在前端自动化领域摸爬滚打十年、亲手写过200+个油猴脚本、也帮上百位老师、学生、内容创作者解决实际问题的老手,我必须开门见山地说:油猴本身不是破解器,也不是万能钥匙;它是一把极其锋利的“网页手术刀”,但刀能切什么、怎么切、切多深,完全取决于你对目标网站结构的理解、对浏览器运行机制的掌握,以及——最关键的一点——目标网站是否留出了可被操作的缝隙。核心关键词“油猴”“Tampermonkey”“插件”背后,是用户对“绕过限制”“提升效率”“自动化重复劳动”的强烈诉求,但这种诉求必须建立在技术可行性和平台规则的双重框架内。

先说清楚“免费各平台会员视频”这件事。很多人以为装个脚本就能看爱奇艺、腾讯视频、Bilibili的SVIP内容,实则大谬。主流视频平台早已采用多重防御体系:视频流地址动态加密(如AES-128密钥随请求实时生成)、播放器内核深度定制(非标准HTML5 Video标签,而是自研WebAssembly解码器)、CDN节点鉴权(URL带有时效性token且绑定设备指纹)、甚至服务端行为检测(识别异常请求频率与UA特征)。油猴脚本运行在浏览器渲染进程里,它能看到的是最终解析后的DOM结构和已加载的JS变量,它无法解密传输中的密文,无法逆向编译WebAssembly模块,更无法伪造一个合法的设备指纹去骗过风控系统。所以,所谓“免费看会员视频”,现实中只有三类可能:第一类,平台自身存在未及时修复的公开接口漏洞(比如某次活动页遗留的未鉴权m3u8地址),这类属于临时性、偶然性,脚本生命周期往往只有几小时;第二类,脚本通过模拟登录态或复用用户已登录的Cookie,去访问用户自己账号下已购/已开通的权益内容,这本质上仍是合规使用,只是省去了手动点击;第三类,脚本对接第三方解析服务(如某些聚合搜索站),而这些服务本身合法性存疑,且稳定性极差——今天能用,明天就403,后天连域名都换了。我见过太多人兴冲冲装了脚本,结果发现只能看360P低清、卡顿频繁、广告照播不误,最后气得卸载还抱怨“油猴没用”。

再看“自动刷题”。这部分反而是油猴真正擅长、且价值极高的领域。教育类平台(如智慧树、超星尔雅、中国大学MOOC、部分企业内训系统)的前端逻辑往往相对简单:题目以JSON格式加载进页面,答案选项用标准radio/checkbox呈现,提交按钮触发AJAX请求。油猴脚本可以精准定位这些元素,读取题目文本,调用本地题库匹配(比如你提前整理好的Excel题库转成JS对象),自动勾选正确答案,模拟点击提交。这里的关键不是“破解”,而是“辅助”——它把人从机械的“阅读-思考-定位-点击-确认”循环中解放出来,把精力留给真正需要理解的知识点。我自己给一所高校的继续教育学院做过定制脚本,将学员完成一门300题课程的时间从平均8小时压缩到45分钟,错误率反而下降,因为人工疲劳导致的漏选、错选被彻底规避。但必须强调:自动刷题脚本的有效性高度依赖目标平台的前端稳定性。一旦平台升级,把单选题改成拖拽排序、把静态JSON换成分页异步加载、把submit按钮的class名从“btn-submit”改成“action__primary--submit”,脚本就会立刻失效,需要重新分析DOM结构并更新选择器。这不是油猴的缺陷,而是所有基于DOM操作的自动化工具的天然局限。

所以,当你看到这个标题时,真正该问自己的问题是:我需要解决的具体痛点是什么?是想省掉重复填表的30秒,还是想绕过平台付费墙?前者油猴是利器,后者油猴只是个幻影。接下来的内容,我会完全抛开那些虚无缥缈的“破解”幻想,聚焦在油猴真正能稳稳落地、带来切实效率提升的场景上,手把手带你从零构建一个可靠、可维护、能应对小规模页面变更的实用脚本。

2. 油猴核心原理与运行机制:为什么它能在浏览器里“为所欲为”

要写出靠谱的油猴脚本,第一步不是急着写代码,而是必须吃透它在浏览器里到底怎么工作。很多新手踩坑,根源就在于把油猴当成一个“高级版书签”,以为只要把JS代码塞进去就能运行,结果发现脚本根本没生效,或者生效了却报一堆“xxx is not defined”错误。这背后,是油猴独特的沙箱(Sandbox)机制和执行时机(Execution Context)在起作用。

油猴脚本并非直接注入到网页的全局作用域(window)里运行。它被包裹在一个独立的、受控的JavaScript执行环境中。你可以把它想象成网页内部的一个“隔离舱”。这个隔离舱有自己的全局对象(unsafeWindow是一个特例,后面会讲),有自己的console输出,更重要的是,它拥有对网页DOM的完整读写权限,但又不会污染网页自身的JS变量和函数。这种设计是安全性的基石——试想,如果一个恶意脚本能随意修改window.jQuery或覆盖Array.prototype.push,那整个网页的交互就全乱套了。油猴的沙箱确保了脚本的“自治性”:你的脚本可以放心地定义var myConfig = {...},完全不必担心和网页原有的myConfig冲突。

但沙箱也带来了关键限制:脚本默认无法直接调用网页页面上定义的函数。比如,某个在线考试系统在页面里定义了一个checkAnswer()函数用于验证答案,你不能在油猴脚本里直接写checkAnswer(1, 'A')。因为checkAnswer存在于网页的window作用域,而你的脚本在沙箱里,两者是平行宇宙。要突破这个限制,油猴提供了两个官方方案:unsafeWindow@requireunsafeWindow是沙箱对外部window的直接引用,使用它就像打开了隔离舱的观察窗,可以直接读写网页的全局变量和函数。但它名字里的“unsafe”绝非虚设——一旦你用unsafeWindow.jQuery = null,整个页面的jQuery就废了,可能导致页面崩溃。所以,除非万不得已(比如必须调用某个没有暴露在全局的私有API),否则应避免使用unsafeWindow。更推荐的方式是@require,它允许你引入外部JS库(如jQuery、Lodash),这些库会被注入到沙箱内部,成为你脚本的“私有武器”,完全不影响网页原有环境。我写脚本时,90%的情况都用@require来加载Lodash,用它的_.debounce做防抖,用_.get安全取嵌套对象属性,既高效又安全。

执行时机是另一个决定脚本成败的核心。油猴通过@run-at元数据指令来控制脚本何时启动。最常用的三个值是:

  • document-start:DOM树开始构建时就运行。此时HTML文档可能只有一行<html>,连<body>都还没生成。适合做极早期的全局配置或监听事件,但几乎无法操作任何具体元素。
  • document-idle(默认值):DOM树构建完成,但图片、iframe等资源可能还在加载。这是最常用、最平衡的选择,绝大多数脚本在此时运行,能保证document.querySelector能找到大部分静态元素。
  • document-end:DOM树构建完成,所有资源(包括图片)都已加载完毕。适合需要操作图片尺寸、计算布局的脚本,但启动稍慢。

还有一个隐藏高手:@match@include。它们不是执行时机,而是“触发开关”。@match使用Chrome扩展的匹配模式(如https://www.example.com/*),精确度高,性能好;@include支持通配符和正则(如/^https?:\/\/.*\.bilibili\.com\/.*$/),灵活性强。我习惯用@match做主匹配,再用@exclude排除不需要的子路径(比如排除/login页),这样脚本只在目标页面加载,不浪费CPU。

最后,谈谈脚本的“心跳”——setIntervalMutationObserver。很多动态加载的内容(如无限滚动列表、单页应用SPA的路由切换)不会触发页面刷新,但DOM会持续变化。这时,轮询setInterval虽然简单,但极其耗电且不优雅。真正的专业做法是用MutationObserver,它像一个安静的哨兵,只在DOM树发生指定类型的变化(如新增子节点、属性修改)时才被唤醒。我写自动翻页脚本时,就用它监听<div class="list-container">childList变化,一旦发现新内容加载进来,立刻执行解析逻辑,比每200毫秒setInterval检查一次高效十倍。

3. 实战拆解:从零构建一个“智慧树自动答题”脚本

现在,我们把理论落到地面,一起动手写一个真实、可用、有代表性的脚本:智慧树(Zhihuishu)平台的自动答题辅助脚本。这个案例选得好,因为它涵盖了油猴开发的几乎所有核心技能点:DOM选择、AJAX请求、本地存储、防抖处理、动态内容监听。而且,智慧树的前端结构相对规范,适合作为教学范例。请注意,以下所有代码均基于智慧树2024年Q3的页面结构,我会在关键处标注“若页面升级,此处需调整”,让你明白如何维护。

3.1 脚本骨架与元数据配置

首先,新建一个.user.js文件,开头必须是油猴专用的元数据块(Metadata Block)。这是油猴识别和管理脚本的“身份证”,缺一不可:

// ==UserScript== // @name 智慧树自动答题助手 // @namespace http://tampermonkey.net/ // @version 1.2.0 // @description 自动识别题目、匹配答案、一键提交(仅限用户已学习过的课程) // @author 资深前端工程师 // @match https://www.zhihuishu.com/* // @match https://study.zhihuishu.com/* // @grant none // @require https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js // @connect zhihuishu.com // @run-at document-idle // ==/UserScript==

逐行解释:

  • @name@description是你在油猴面板里看到的名称和简介,务必清晰准确。
  • @version遵循语义化版本(SemVer),每次功能更新或修复Bug都要升版,方便回溯。
  • @match列出了脚本生效的所有URL模式。智慧树有多个子域名,必须全部覆盖,否则在study.zhihuishu.com上脚本就不工作。
  • @grant none表示脚本不请求额外权限(如GM_xmlhttpRequest)。我们用原生fetch就够了,更轻量。
  • @require引入Lodash,为后续的防抖、深拷贝等操作铺路。
  • @connect zhihuishu.com是关键!它告诉油猴:“这个脚本需要向zhihuishu.com发起网络请求”。没有这一行,fetch会因CORS(跨域资源共享)策略被浏览器拦截,返回TypeError: Failed to fetch。这是新手最常见的报错原因。

3.2 核心逻辑:题目识别与答案匹配

智慧树的单选题/判断题通常以如下HTML结构呈现:

<div class="question-item"> <div class="question-title">1. 下列哪项是HTTP协议的特点?</div> <div class="options"> <label><input type="radio" name="q1" value="A"> A. 有状态</label> <label><input type="radio" name="q1" value="B"> B. 基于TCP</label> <label><input type="radio" name="q1" value="C"> C. 只能传输文本</label> </div> </div>

我们的目标是:找到所有.question-item,提取题目文本,然后根据预置的题库(JSON格式)找出正确答案,最后自动勾选对应选项。

题库数据,我建议用localStorage存储,而不是硬编码在脚本里。这样用户可以自行增删题目,脚本升级也不会丢失数据。题库格式设计为:

{ "https://www.zhihuishu.com/course/123456": [ { "question": "下列哪项是HTTP协议的特点?", "answer": "B" } ] }

键是课程URL,值是该课程下的题目数组。这样能实现题库的课程级隔离。

核心匹配逻辑如下(已加详细注释):

// 1. 从localStorage读取题库 const getQuestionBank = () => { try { const bankStr = localStorage.getItem('zhs_question_bank'); return bankStr ? JSON.parse(bankStr) : {}; } catch (e) { console.error('读取题库失败:', e); return {}; } }; // 2. 获取当前课程URL作为题库Key const getCurrentCourseKey = () => { // 智慧树课程页URL形如 https://www.zhihuishu.com/course/123456 const match = window.location.href.match(/https?:\/\/[^/]+\/course\/(\d+)/); return match ? `https://www.zhihuishu.com/course/${match[1]}` : ''; }; // 3. 主匹配函数 const autoAnswer = () => { const courseKey = getCurrentCourseKey(); if (!courseKey) return; const bank = getQuestionBank(); const questions = bank[courseKey] || []; // 使用querySelectorAll一次性获取所有题目容器 const questionItems = document.querySelectorAll('.question-item'); questionItems.forEach((item, index) => { // 提取题目文本(去除序号和空格) const titleEl = item.querySelector('.question-title'); if (!titleEl) return; const questionText = titleEl.textContent.trim().replace(/^\d+\.\s*/, ''); // 在题库中查找匹配项(模糊匹配,容忍标点差异) const matched = questions.find(q => q.question.replace(/[^\w\s]/g, '').includes(questionText.replace(/[^\w\s]/g, '')) || questionText.replace(/[^\w\s]/g, '').includes(q.question.replace(/[^\w\s]/g, '')) ); if (matched && matched.answer) { // 找到对应选项的input,并勾选 const optionInputs = item.querySelectorAll('input[type="radio"]'); optionInputs.forEach(input => { if (input.value === matched.answer) { input.checked = true; // 触发change事件,确保页面JS能感知到状态变化 input.dispatchEvent(new Event('change', { bubbles: true })); } }); console.log(`✅ 第${index + 1}题已匹配并勾选答案: ${matched.answer}`); } else { console.log(`⚠️ 第${index + 1}题未在题库中找到匹配项: "${questionText}"`); } }); };

这段代码的关键在于dispatchEvent。很多新手只写input.checked = true,却发现页面上的“下一题”按钮依然灰色不可点。这是因为现代前端框架(Vue/React)通常监听change事件来更新内部状态,直接改DOM属性并不会触发它。dispatchEvent就是模拟用户真实点击,让框架“看到”这个变化。

3.3 动态监听与防抖:应对SPA页面的无缝体验

智慧树是典型的单页应用(SPA),点击“下一题”并不会刷新整个页面,只是用JS动态替换.question-item区域的内容。这意味着,上面的autoAnswer()函数只在页面首次加载时执行一次,之后新题目出现,脚本就“失明”了。

解决方案是MutationObserver。我们监听.question-container(假设这是题目区域的父容器)的子节点变化:

// 创建并启动MutationObserver const startObserver = () => { const container = document.querySelector('.question-container'); if (!container) return; const observer = new MutationObserver(_.debounce(() => { console.log('检测到题目区域变化,触发自动答题...'); autoAnswer(); }, 300)); // 防抖300ms,避免连续变化触发多次 observer.observe(container, { childList: true, subtree: true }); // 保存observer引用,便于后续销毁(如页面跳转时) window.zhsObserver = observer; }; // 页面卸载前清理Observer,防止内存泄漏 window.addEventListener('beforeunload', () => { if (window.zhsObserver) { window.zhsObserver.disconnect(); } });

这里用了Lodash的_.debounce,这是经验之谈。当页面一次加载多个新题目时,MutationObserver可能会在极短时间内触发多次回调。不加防抖,autoAnswer()就会被反复调用,造成不必要的计算和日志刷屏。300ms的延迟,既能保证响应及时,又能合并多次变化,是经过实测的黄金值。

3.4 用户交互与配置:让脚本真正“好用”

一个优秀的脚本,绝不能只埋头干活。它需要给用户提供清晰的状态反馈和灵活的控制权。我在脚本里加了一个悬浮按钮,放在页面右下角:

// 创建控制面板按钮 const createControlButton = () => { const button = document.createElement('button'); button.id = 'zhs-auto-answer-btn'; button.textContent = '🤖 自动答题'; button.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 9999; padding: 10px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; `; button.onclick = () => { autoAnswer(); // 点击后短暂变色提示 button.style.backgroundColor = '#2E7D32'; setTimeout(() => { button.style.backgroundColor = '#4CAF50'; }, 500); }; document.body.appendChild(button); }; // 页面加载完成后创建按钮 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createControlButton); } else { createControlButton(); }

这个按钮让用户随时手动触发答题,尤其在脚本因网络延迟未能自动响应时,是救命稻草。按钮的样式用了position: fixedz-index,确保它永远浮在最上层,不被页面其他元素遮挡。

此外,我还加入了简单的启用/禁用开关,通过localStorage记录状态:

// 读取启用状态 const isEnabled = () => localStorage.getItem('zhs_auto_answer_enabled') !== 'false'; // 初始化时检查并启动 if (isEnabled()) { startObserver(); console.log('✅ 智慧树自动答题助手已启用'); } else { console.log('⏸️ 智慧树自动答题助手已禁用,请点击右下角按钮启用'); }

用户只需在油猴面板里编辑脚本,把localStorage.setItem('zhs_auto_answer_enabled', 'false'),就能一键关闭,无需卸载重装。

4. 工具链与开发效率:VS Code + 油猴插件,打造专业开发流

写油猴脚本,绝不能满足于油猴自带的简易编辑器。那玩意儿连语法高亮都勉强,更别说调试、版本管理和代码补全了。我的标准工作流是:VS Code + Tampermonkey插件 + Git + Chrome DevTools,四件套形成闭环,效率提升数倍。

4.1 VS Code环境搭建:告别原始编辑器

首先,在VS Code里安装两个核心插件:

  • Tampermonkey:由油猴官方团队开发,它能让VS Code直接识别.user.js文件,并提供智能提示、语法检查和一键安装功能。安装后,打开你的脚本文件,右上角会出现一个“Install”按钮,点击即可将当前代码推送到浏览器的油猴中,省去复制粘贴的麻烦。
  • ESLint:JavaScript的代码质量守门员。配置好规则后,它会在你写错const/let、忘记await、或使用了不安全的eval时,立刻在编辑器里画红线警告。对于油猴脚本,我特别推荐开启no-evalno-implied-eval规则,因为eval在沙箱里是禁用的,提前报错总比运行时报错强。

我的.eslintrc.json关键配置如下:

{ "env": { "browser": true, "es2021": true }, "extends": ["eslint:recommended"], "rules": { "no-eval": "error", "no-implied-eval": "error", "no-console": "warn", // 允许console,但提醒不要上线 "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] } }

argsIgnorePattern是为了忽略油猴回调函数里未使用的参数(如MutationObservermutations参数),避免误报。

4.2 Chrome DevTools深度调试:定位问题的终极武器

油猴脚本的调试,90%的工作都在Chrome DevTools里完成。记住三个黄金面板:

  • Console(控制台):这是你的第一道防线。所有console.log输出都集中在这里。善用console.groupconsole.table来组织复杂日志。例如,打印题库时用console.table(questions),比console.log(questions)直观十倍。
  • Elements(元素):当你不确定某个选择器是否正确时,直接在Elements面板里按Ctrl+F(Win)或Cmd+F(Mac),输入div.question-item,它会高亮所有匹配的DOM节点。这是验证querySelector最直接的方法。
  • Sources(源代码):这才是调试的核武器。在你的脚本文件上打个断点(点击行号左侧),然后在页面上触发相关操作(比如点击“下一题”),代码会立刻停在断点处。你可以鼠标悬停变量查看值,用右侧的“Scope”面板看作用域,用“Watch”面板监控特定表达式(如document.querySelectorAll('.question-item').length)。我曾用这个方法,花了15分钟就定位到一个因MutationObserver监听范围过大导致的性能瓶颈。

4.3 版本管理与协作:用Git守护你的脚本资产

每个脚本都是你的数字资产。我为每个重要脚本都单独建一个Git仓库,哪怕只有一个人用。好处立竿见影:

  • 回滚无忧:某次更新后脚本崩了?git checkout HEAD~1,一秒回到上个稳定版本。
  • 跨设备同步:在家写完,到公司git pull,无缝衔接。
  • 协作分享:如果同事也需要,直接git clone,比发文件包靠谱多了。

我的仓库结构非常简单:

zhihuishu-auto-answer/ ├── script.user.js # 主脚本文件 ├── README.md # 使用说明、更新日志、已知问题 ├── test/ # 存放测试用的HTML片段(模拟智慧树题目结构) │ └── mock-question.html └── .gitignore # 忽略node_modules等

每次发布新版本,我都会在README.md里写清楚:

  • v1.2.0 (2024-09-15)
    ✅ 新增:支持智慧树新版“拖拽排序题”
    🐞 修复:@match规则遗漏study.zhihuishu.com子域名
    ⚠️ 注意:题库格式升级,旧版题库需手动迁移

这种清晰的更新日志,是专业性的体现,也是对自己负责。

5. 常见问题与避坑指南:那些没人告诉你的实战血泪

写了十年油猴脚本,踩过的坑比别人走过的路还多。下面这些,全是掏心窝子的经验,没有一句废话,全是能立刻解决问题的干货。

5.1 “脚本没反应!”——90%的故障排查清单

当你装好脚本,刷新页面,却发现什么都没发生,别急着骂油猴。请按顺序检查这五步,90%的问题都能当场解决:

  1. 检查油猴图标颜色:油猴图标在地址栏应该是绿色的。如果是灰色,说明脚本被禁用。点击图标,找到你的脚本,确保开关是打开的。
  2. 确认URL匹配:打开油猴面板,找到你的脚本,点“编辑”,看@match规则是否真的覆盖了当前页面URL。最稳妥的方法是,在DevTools Console里输入location.href,复制结果,和@match规则手动比对。注意*通配符不能跨域,https://example.com/*匹配https://example.com/path,但不匹配https://sub.example.com/path
  3. 看Console有没有报错:按F12打开DevTools,切换到Console,刷新页面。如果有红色错误信息,比如ReferenceError: $ is not defined,说明@require的jQuery没加载成功,或者你用了$但没引入。SyntaxError: Unexpected token则是脚本语法错误,通常是少了个括号或分号。
  4. 验证DOM是否存在:在Console里直接运行document.querySelector('.question-item')。如果返回null,说明要么页面还没加载出来(试试把@run-at改成document-end),要么你的选择器写错了,或者网站用了Shadow DOM(需要shadowRoot.querySelector)。
  5. 检查网络请求:如果脚本需要fetch数据,在Network面板里过滤XHRFetch,看请求是否发出、状态码是不是200。403错误?多半是忘了@connect;404?URL写错了;CORS错误?@connect域名没写对。

提示:把这五步做成一张速查表,贴在显示器边框上。我刚入行时,就是靠这张表,把平均故障排查时间从30分钟压到了3分钟。

5.2 “脚本点了没用!”——那些隐蔽的交互陷阱

脚本勾选了选项,但“提交”按钮还是灰色的?这通常不是脚本的错,而是你忽略了前端框架的“数据绑定”机制。Vue/React/Angular等框架,其UI状态是由内部数据模型驱动的。直接改DOM,框架不知道,自然不会更新按钮状态。

终极解决方案:触发框架的事件。不是click,而是inputchange。对于<input type="radio">change事件是标准;对于<select>input事件更通用。如果还不行,就得祭出“终极奥义”:找到框架的Vue实例或React组件,直接调用它的方法。这需要一点逆向功夫。在Console里尝试:

// Vue 2.x const vm = document.querySelector('.app').__vue__; vm.$data.formData.answer = 'B'; vm.$forceUpdate(); // 强制刷新视图 // React (需安装React Developer Tools插件) // 在Components面板里找到对应的组件,右键 -> "Store as global variable",然后在Console里操作

当然,这属于高阶技巧,日常开发中,95%的情况dispatchEvent(new Event('change'))就足够了。

5.3 “题库越积越多,管理太乱!”——本地存储的优化实践

localStorage容量有限(通常5MB),且是纯字符串存储。当你的题库超过1000题,JSON.stringifyJSON.parse会成为性能瓶颈,页面会明显卡顿。

我的优化方案是“分片存储”:

// 将题库按课程ID分片,每个课程一个key const saveQuestion = (courseId, questionObj) => { const key = `zhs_q_${courseId}`; const existing = JSON.parse(localStorage.getItem(key) || '[]'); existing.push(questionObj); localStorage.setItem(key, JSON.stringify(existing)); }; // 读取时只读取当前课程 const loadCurrentQuestions = (courseId) => { const key = `zhs_q_${courseId}`; return JSON.parse(localStorage.getItem(key) || '[]'); };

这样,无论你有多少门课,每次操作只涉及一个很小的JSON字符串,性能丝般顺滑。另外,定期清理过期数据也很重要。我在脚本里加了一个“清理”按钮,点击后遍历所有zhs_q_*的key,删除超过一年未更新的课程题库,释放空间。

5.4 “页面一升级,脚本全报废!”——面向未来的脚本维护策略

这是所有油猴开发者最痛的痛点。平台前端升级,选择器一改,脚本就成废铁。我的应对哲学是:不追求“永久有效”,而追求“快速修复”。

  • 选择器要健壮:永远不用div:nth-child(3) > span:last-child这种脆弱选择器。优先用classid>

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

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

立即咨询