倍速刷课脚本原理与实现:从playbackRate到油猴脚本实战 📅 发布时间:2026/9/19 11:52:51 👁 浏览次数: 先说结论这类“倍速刷课脚本”本质上是利用浏览器对 video 元素的播放速率控制和事件机制把“看视频”这个动作自动化。但我也得把丑话说在前面——如果你只是为了让后台记录多几个学时那这个脚本确实能“骗”过系统但如果你想真正学点东西倍速只是个辅助工具别本末倒置。这篇文章我会从原理、实现、避坑三个角度完整拆解一遍把我自己在实际调试中踩过的坑和总结的参数选择逻辑都写出来方便你直接照着改。1. 这类脚本到底在解决什么问题1.1 为什么会有“刷课”这个需求在线课程平台一般会在播放器里内置倍速选项但基本都有限制有些只支持到 1.5 倍有些必须等当前小节播完才能点下一节还有些平台的“自动连播”逻辑做得很弱播完一个 5 分钟的视频就停在结束页需要手动点下一步。于是“倍速刷课脚本”就成了一个刚需——它在浏览器端接管这些重复操作把倍速拉到浏览器允许的上限并在视频结束后自动点击下一节形成一个完整的循环。当然如果课程平台后台做了防刷检测比如记录鼠标移动、页面焦点、答题弹窗那脚本还要处理这些额外逻辑。不过从我接触过的多数场景来看针对内部培训类课程平台更多是检查“播放进度到达 100%”和“播放心跳是否连续”并不会做太复杂的风控。1.2 它和普通“抢票脚本”“挂机脚本”的差异很多人在热搜词里搜“脚本”时会混着看到抢票脚本、游戏脚本、刷课脚本。但它们的技术栈完全不同。游戏脚本通常需要读内存、模拟键鼠属于系统级自动化抢票脚本需要处理网络请求、验证码识别。而倍速刷课脚本只需要访问页面里的 video 元素和 DOM 节点所有操作都在浏览器内部完成不需要额外安装环境也不会触发系统安全策略。正是因为它的“轻”所以最适合用浏览器扩展或者油猴脚本这类前端方案实现。我后面会重点讲这两种方案的取舍。2. 整体设计和方案选型2.1 三个核心模块加速、自动跳转、保活不管脚本外表长什么样拆开来看无非三件事。第一加速播放。核心 API 是video.playbackRate这个属性是 HTML5 video 元素自带的标准属性绝大多数浏览器都支持。我们的脚本只需要找到页面里正在播放的 video 节点把这个值设成 2、4、8 甚至 16浏览器就会按这个倍速播放。但播放速率过高时部分平台会通过ratechange事件检测到异常所以脚本里一般会做“渐进式加速”或者“随机小幅调整”避免被判定为异常行为。第二自动跳转。在线课程通常会切成小节视频播完后播放器可能触发ended事件。脚本要监听这个事件然后主动点击页面上的“下一节”按钮或者通过 URL 跳转进入下一个课时。这里最常见的坑是按钮 DOM 结构不固定有的平台按钮文字是“下一节”有的是“继续学习”有的隐藏在悬浮层里所以要写多个 selector 做兼容。第三保活。很多平台有“闲置检测”如果你长时间没有鼠标键盘操作它会暂停播放或弹窗确认。脚本一般会定时模拟一点轻微交互——但这里提醒一句模拟交互这件事在部分平台规则里属于绕过平台限制别用在有监考的考试场景里自己衡量风险。2.2 为什么优先选油猴脚本而不是独立浏览器插件我当时第一个想法是写 Chrome 扩展毕竟 Manifest V3 已经是主流了。但后来斟酌了一下还是用油猴脚本先做原型。原因有这么几点油猴脚本只需要一个管理后台和一段 JavaScript不需要打包、不需要申请开发者模式、不需要处理扩展的权限声明。油猴脚本天然支持跨域页面注入可以直接匹配你要刷课的域名不用单独配置content_scripts的matches权限。改代码之后刷新页面就能生效开发调试非常快。独立扩展也有它的优势比如可以使用chrome.debuggerAPI 做更底层的控制或者绕过页面里的一些 JS 对playbackRate的检测。但大部分场景不需要这么复杂我建议你先用油猴跑通逻辑真遇到限制再考虑扩展升级。2.3 工具选型Tampermonkey vs Violentmonkey油猴脚本管理器里我用得最多的是 Tampermonkey其次是 Violentmonkey。两者在脚本兼容性上几乎没有差别但 Tampermonkey 的GM_setValue/GM_getValue接口更稳定适合保存配置项比如“每个视频最多倍速多少倍”“是否开启自动下一节”这类开关。Violentmonkey 更加轻量内存占用小如果你的电脑配置比较老旧可以考虑它。不过我这里所有示例代码都用标准 GM API两个管理器都可以直接跑。3. 核心细节解析与实操要点3.1 页面里 video 元素的定位所有刷课脚本的基础是找到 video 节点。大多数在线课程页面都只有一个主播放器所以最简单的写法是const video document.querySelector(video); if (!video) { console.warn(未找到 video 节点请检查页面是否已加载播放器); return; }但有些平台会把弹窗广告、小窗预览也做成 video 标签导致选错节点。这时候需要进一步筛选判断标准一般是视频的duration是否合理比如大于 60 秒以及currentTime是否大于 0。我在脚本里写了一个工具函数function findMainVideo() { const videos Array.from(document.querySelectorAll(video)); return videos.sort((a, b) { const aScore (a.duration 60 ? 1 : 0) (a.currentTime 0 ? 1 : 0); const bScore (b.duration 60 ? 1 : 0) (b.currentTime 0 ? 1 : 0); return bScore - aScore; })[0] || null; }这个方法不是万能的但实际使用下来命中率很高。3.2 playbackRate 的边界和浏览器限制HTML5 video 的playbackRate理论上可以设置为任意正数但浏览器对不同倍速的处理性能不同。Chrome 在 2 倍以内都是硬件加速播放超过 4 倍之后会明显出现音画不同步再往上视频帧可能会被丢得厉害。如果只是刷学时一般不需要声音所以最稳妥的做法是先把视频muted静音再把倍速拉高。静音状态下浏览器可以不解码音频性能开销大幅下降甚至可以快到 16 倍以上。如果平台检测音量状态或者要求必须在非静音状态下播放那就另说了。你自己权衡。3.3 自动点击“下一节”的逻辑自动跳转会根据平台不同而不同我整理了一下常见实现方式监听 ended 事件video.addEventListener(ended, next)然后调用点击函数。定时轮询 currentTime每隔几秒检查video.currentTime video.duration - 0.5就认为播放快结束了提前准备下一节。观察界面变化用MutationObserver监听 DOM 变化如果“下一节”按钮出现就自动点击。ended事件最自然但有个问题如果平台在视频结束后还要执行自己的回调函数比如关闭播放器、弹出完成提示那么脚本的next函数可能执行太早点击不到按钮。所以我更推荐“定时轮询 延时点击”的组合。function scheduleNext() { if (video.duration 0 || isNaN(video.duration)) return; const remain video.duration - video.currentTime; if (remain 0.8) { setTimeout(() { clickNextButton(); }, 1000); } } setInterval(scheduleNext, 1000);这里的 0.8 秒和 1 秒延时是我实测下来比较稳的窗口给平台一点处理时间又不至于拖太久。3.4 配置项设计不要写死参数很多人在写脚本时喜欢把所有参数写死在代码里这个习惯不好。因为不同课程的视频长度、播放器行为、倍速上限都不一样。我建议至少留出这几个配置项const CONFIG { playbackRate: 4, // 目标倍速 useMuted: true, // 是否静音播放 autoNext: true, // 是否自动切换下一节 detectionInterval: 1000, // 轮询间隔毫秒 maxRetry: 3, // 自动点击失败后的重试次数 };用GM_getValue读取的话还可以在浏览器里随时改配置不用打开编辑器const playbackRate GM_getValue(playbackRate, 4);3.5 防检测思路不要过于激进这里我不教你怎么作弊但可以把原理说清楚平台检测的基本方法就是看playbackRate是否异常、看心跳间隔是否过短、看是否有用户交互。所以如果脚本只是把倍速提高到 2 倍同时保持正常的鼠标移动记录一般不会被判为异常。但如果你直接设成 16 倍而且一分钟内刷完一小时的视频这种明显异常的行为任何平台都有理由不信任你的学习记录。常见的“温和加速”策略是先以 1 倍速播放几秒然后逐个递增到目标倍速每个档位停留 1 到 2 秒模拟真实用户手动调整倍速的过程。这样即便平台有速率记录看起来也更自然。4. 实操过程与核心环节实现4.1 从零搭建一个油猴脚本油猴脚本的开头有一段元信息注释需要声明脚本名称、匹配页面、依赖的接口。我写了一个最小可用的版本// UserScript // name 在线课程倍速助手 // namespace https://your.namespace/ // version 0.1.0 // description 对在线课程视频进行倍速播放与自动下一节适合合法合规的个人学习场景 // author your-name // match https://course.example.com/* // grant GM_getValue // grant GM_setValue // run-at document-idle // /UserScript (function () { use strict; const CONFIG { playbackRate: GM_getValue(playbackRate, 4), useMuted: GM_getValue(useMuted, true), autoNext: GM_getValue(autoNext, true), detectionInterval: GM_getValue(detectionInterval, 1000), }; function findMainVideo() { const videos Array.from(document.querySelectorAll(video)); return videos.sort((a, b) { const aScore (a.duration 60 ? 1 : 0) (a.currentTime 0 ? 1 : 0); const bScore (b.duration 60 ? 1 : 0) (b.currentTime 0 ? 1 : 0); return bScore - aScore; })[0] || null; } function applyPlaybackRate(video) { if (CONFIG.useMuted) { video.muted true; } video.playbackRate CONFIG.playbackRate; console.log([倍速助手] 当前倍速: ${video.playbackRate}); } function clickNextButton() { const selectors [ .next-btn, .next-button, button:contains(下一节), a[href*next], ]; for (const selector of selectors) { const el document.querySelector(selector); if (el) { el.click(); console.log([倍速助手] 已点击下一节: ${selector}); return true; } } return false; } function monitorVideo() { const video findMainVideo(); if (!video) { setTimeout(monitorVideo, 2000); return; } applyPlaybackRate(video); setInterval(() { const current findMainVideo(); if (!current) return; if (Math.abs(current.playbackRate - CONFIG.playbackRate) 0.1) { applyPlaybackRate(current); } if (CONFIG.autoNext current.duration 0) { const remain current.duration - current.currentTime; if (remain 0.8) { setTimeout(() clickNextButton(), 1000); } } }, CONFIG.detectionInterval); } setTimeout(monitorVideo, 1500); })();这段代码是一个完整的骨架。它做的事是在页面加载完成后每隔 1.5 秒找一次 video 节点找到后就设置倍速和静音之后每 1 秒检查一次播放状态如果倍速被平台重置就重新设置如果播放快结束就准备点击下一节。4.2 为什么要在 setInterval 里重复设置倍速有一个细节很容易被忽略很多平台播放器会在切集之后重新创建 video 节点并且把 playbackRate 重置成 1。所以如果你只在初始化时设置一次倍速那么每换一集倍速就失效了。我在代码里每隔一秒检测一次当前倍速发现不对就重新设置这样切集后也能自动生效。但注意这个逻辑也会带来一个问题如果平台允许用户在播放器界面手动调节倍速脚本会立刻把用户的选择改回去。所以我在用的时候会把脚本里的playbackRate配置成和平台自带倍速一致这样既不会冲突也避免刷课痕迹太明显。4.3 结合ended事件和轮询的混合策略我前面提到了ended和轮询实际项目中我是混用的。ended作为“最后一击”轮询作为“兜底”。video.addEventListener(ended, () { setTimeout(() clickNextButton(), 500); });ended监听的好处是实时性高视频一播完就触发不用等待轮询周期。但它的风险是点击执行太早平台还没来得及把“下一节”按钮渲染出来。延时 500 毫秒是折中值——太短按钮可能没渲染太长平台可能已经弹出了“已完成”遮罩。4.4 处理“弹窗确认继续观看”的场景不少平台会在连续观看一段时间后弹出“是否继续观看”的确认框。这种弹窗不属于 video 节点需要用 DOM 查找和点击。常见文案是“继续”“确定”“知道了”“关闭”。为了让脚本具备一定通用性我维护了一个常见按钮文案的映射function dismissPopup() { const texts [继续观看, 继续学习, 知道了, 确定, 我知道了, 关闭]; const buttons Array.from(document.querySelectorAll(button, a, .btn, .el-button)); for (const btn of buttons) { const text btn.textContent.trim(); if (texts.some((t) text.includes(t))) { btn.click(); return true; } } return false; }这个函数不能太激进否则容易误点页面里的其他按钮。建议在弹窗出现时才调用比如通过MutationObserver监听弹窗节点出现再执行 dismiss。4.5 减少资源占用不要无限开 setInterval脚本跑久了如果页面里有多个定时器浏览器会越来越卡。尤其是一些课程页面本身就很臃肿存在内存泄漏。我一般会在每次进入新一集时先清除旧的定时器再重建新的定时器避免监听器叠加。let timer null; function resetTimer(video) { if (timer) clearInterval(timer); timer setInterval(() { // 检查逻辑 }, 1000); }如果你发现脚本运行几小时之后页面卡顿优先检查是不是定时器叠加了。5. 常见问题与排查技巧实录5.1 明明设置了倍速但播放速度没变这个问题最常见的原因是页面里有多个 video 节点脚本选到了错误的对象。比如视频封面预览是一个 3 秒的无声短视频主课程视频是 40 分钟的两个都有 video 标签。如果脚本匹配到的是预览视频你设置倍速自然看不到效果。排查方法很简单在控制台执行document.querySelectorAll(video).forEach((v, i) { console.log(i, v.duration, v.src, v.currentTime); });看看哪些 video 节点的duration大于 300 秒然后手动把playbackRate设置到那个节点上。如果立刻生效说明是定位问题回去改进findMainVideo的排序逻辑。5.2 设置了倍速但播放几秒后又被重置这种情况多半是平台播放器自己维护了一套状态比如用 React 或 Vue 框架当视频的元数据加载完成或播放状态变化时会重新渲染播放器属性把playbackRate重置为默认值。解决方案就是我前面说的在定时器里持续检测并重置倍速。同时也要考虑是不是平台特意做的“反倍速”功能这种情况下你只能接受平台允许的倍速上限。5.3 自动点击下一节无效自动点击无效的原因通常是按钮不是真正的button或a元素而是div或span加了个 click 事件。这时候直接调用el.click()可能不会触发事件因为该元素的 click 事件是通过事件委托绑定的。此时可以改用派发鼠标事件的方式function triggerClick(el) { const event new MouseEvent(click, { bubbles: true, cancelable: true, view: window, }); el.dispatchEvent(event); }这个方式更接近真实用户点击可以绕过一部分框架封装的事件绑定限制。5.4 脚本正常执行但页面卡死页卡死一般有两个原因。第一轮询间隔太短setInterval每次都会执行大量 DOM 查询比如每秒循环多次document.querySelectorAll(*)资源消耗很大。第二视频处于高倍速且连续播放时如果浏览器不静音音频解码会占用大量 CPU。建议把useMuted设为 true并把轮询间隔控制在 500 毫秒以上。另外不要用document.querySelector(video)每次都重新查找全页节点尤其是页面结构复杂时可以把找到的节点缓存下来只在视频结束时重新查找。5.5 切换课程页面后脚本失效match规则如果只匹配了某个具体路径那么换一个课程路径就会失效。比如你写的是https://course.example.com/lesson/*但新课程页面的 URL 是https://course.example.com/unit/123那就匹配不上。解决方法是把match配得宽一点// match https://course.example.com/*如果平台登录后才能看课程还要注意脚本的执行时机。run-at document-idle能保证 DOM 已加载但如果播放器是异步加载的可能 video 节点在脚本执行时还没出现因此需要轮询等待。5.6 平台有“播放完成后手动确认”的机制有些平台在每节课播放完成后会出现一个“我已观看完毕”的按钮需要用户点击后才会记录进度。这种情况脚本里只点击“下一节”是不够的还需要识别这个确认按钮。我的做法是维护一个“确认按钮候选列表”定时扫描页面上的按钮文案一旦匹配“我已观看”“完成观看”等关键词就自动点击。这种场景已经比较贴近“绕过平台规则”了具体是否使用你自己拿捏我只说技术实现上是可以做到的。5.7 长时间运行被平台踢下线长时间高频操作确实可能触发登录态失效原因不一定是脚本导致很多平台的会话有效期本身就只有几个小时。如果被踢下线脚本不会自动重新登录这时候需要配合浏览器的“记住登录状态”功能或者手动重新登录后再打开脚本。如果发现是被风控判定为异常那就要检查是不是倍速设得太高、点击间隔太固定、操作频率像个机器人。适当在操作间加入随机延时会降低被检测的概率。6. 经验总结与最后的建议从技术角度讲倍速刷课脚本是一个非常好的前端自动化练手项目它涵盖了 DOM 查询、事件监听、定时器管理、MutationObserver、跨域脚本注入等知识点做完之后你基本上就能应对大部分网页自动化场景。但回到一开始说的——刷课之前先问问自己是想要知识还是只想要一个“已完成”的状态。如果只是后者脚本确实能帮你省时间但省下来的时间如果都拿去做别的事了那学习的意义也就没了。我个人的做法是用脚本处理掉那些无聊的、纯凑学时的安全培训视频但凡是和自己专业相关的课程我会把倍速控制在 1.5 倍以内并且不开静音认认真真看完。前者是工具理性后者是对自己负责。脚本只是工具用得好是效率用不好是自欺欺人。最后再分享一个小技巧如果你在改脚本时发现控制台报错Cannot read properties of null先别急着换库多数情况下只是页面还没渲染完把代码放到setTimeout或者MutationObserver里再跑一次问题就自然消失了。调试网页自动化脚本耐心比技术重要得多。