基于Tampermonkey的网页视频自动连播脚本开发实战
1. 项目概述:当网页视频播放遇上自动化脚本
如果你经常在网页上看视频,尤其是那些需要手动点击“下一集”或者在不同视频源之间切换的场景,那么“JavaScript自动切换并播放视频”这个需求你一定不陌生。这听起来像是一个懒人工具,但背后其实是一个典型的浏览器自动化场景,而Tampermonkey(油猴脚本管理器)正是实现这个想法的绝佳平台。简单来说,这个项目就是利用Tampermonkey,编写一段JavaScript脚本,让它像一只无形的手,自动帮你完成视频播放页面的点击、切换、播放等操作,从而解放你的双手。
这个想法的应用场景远比想象中广泛。比如,你在一个在线学习平台观看系列课程,每节课结束后都需要手动点击“下一节”,几十节课下来,这个重复动作就变得非常烦人。又或者,你在一个聚合了多个视频源的网站上追剧,每次播放完一集,都需要从一堆“播放源”按钮中重新选择并点击,流程繁琐。更进阶一点,有些网站的视频播放列表是动态加载的,或者有复杂的鉴权逻辑,手动操作不仅低效,还可能因为网络波动导致中断。通过编写一个Tampermonkey脚本,我们可以将这些重复、规律性的操作自动化,实现“打开页面,泡杯咖啡,回来时视频已经自动播到最新一集”的惬意体验。
这个项目的核心价值在于“个性化”和“自动化”。它不依赖于网站官方提供的功能(很多网站确实没有完善的自动连播),而是由用户自己定义规则,去适配千变万化的网页结构。这要求我们不仅要懂JavaScript,还要懂如何与网页文档对象模型(DOM)交互,如何处理异步加载,如何应对网站反爬或结构变动。接下来,我将以一个模拟的“在线视频学习平台”为例,拆解如何从零开始构建这样一个自动化脚本,分享其中的核心思路、技术细节以及我踩过的那些坑。
2. 核心思路与方案设计
2.1 需求分析与技术选型
要实现“自动切换并播放”,我们需要将这个大目标拆解成几个可执行的子任务:
- 监控播放状态:脚本需要知道当前视频何时播放完毕。
- 定位目标元素:播放完毕后,需要找到“下一集”按钮或下一个视频源的链接。
- 执行切换操作:模拟用户点击,触发页面跳转或视频源切换。
- 处理新页面/新播放器:页面切换或播放器更新后,需要重新绑定监控事件,形成循环。
- 增加容错与控制:处理网络延迟、元素加载慢、页面结构变化等异常情况,并提供手动开关。
基于这些任务,技术选型就很明确了:
- 平台:Tampermonkey。它是浏览器扩展,提供了一个安全、隔离的环境来运行用户脚本(UserScript),可以方便地管理脚本、设置匹配的网址规则(
@match),并拥有GM_*系列API来增强功能。 - 核心语言:JavaScript。这是与浏览器和网页交互的唯一语言。
- 关键API:
MutationObserver:用于监听DOM变化,应对动态加载内容的网站。addEventListener:监听视频元素的ended、timeupdate等事件。setTimeout/setInterval:用于轮询检查元素是否存在(作为MutationObserver的备选或补充)。click()方法:模拟用户点击。GM_notification(可选):用于在操作成功或失败时发送桌面通知。
为什么不选用其他自动化工具如Selenium或Puppeteer?因为它们更适用于完整的浏览器自动化测试或爬虫,需要独立运行环境,无法像Tampermonkey脚本这样轻量、持久地附着在特定网页上,随开随用。Tampermonkey脚本的优势在于“用户视角”的自动化,无缝集成到日常浏览中。
2.2 脚本架构设计
一个健壮的自动播放脚本不应该是一堆线性执行的代码。我倾向于采用一种“状态机”或“事件驱动”的架构,让逻辑更清晰。
基本工作流如下:
- 初始化:脚本在匹配的页面加载后运行。首先检查页面上是否存在视频播放器元素(如
<video>或某些特定播放器的容器div)。 - 绑定监听器:找到视频元素后,为其绑定
ended事件监听器。 - 事件触发:当视频播放结束时,
ended事件被触发。 - 寻找下一集:在事件处理函数中,使用
document.querySelector或document.querySelectorAll配合特定的CSS选择器,寻找“下一集”按钮。这个选择器需要你事先分析目标网页的HTML结构来确定。 - 执行切换:如果找到按钮,调用其
click()方法。如果没有找到,则可能通过MutationObserver等待按钮出现,或者尝试寻找下一个视频的页面链接(<a href="...">)并进行跳转(window.location.href = newUrl)。 - 循环与重置:如果是页面跳转,新页面会再次触发脚本初始化。如果是单页应用(SPA)仅更新了播放器,则需要在新的播放器元素上重新执行步骤1和2,绑定新的监听器。
- 异常处理:在整个过程中,任何一个环节失败(如元素找不到、点击无效),都应有相应的错误处理,比如重试几次、发出通知或直接停止脚本,避免陷入无限错误循环。
这个架构的核心是“监听 -> 响应 -> 重置监听”的循环。难点往往在于如何精准地定位元素,以及如何处理网站复杂的动态加载逻辑。
3. 核心代码实现与详解
下面,我将分模块展示一个基础但功能完整的Tampermonkey脚本代码,并逐段解释。我们假设目标网站是一个简单的学习平台,视频播放完毕后,右侧有一个课程列表,当前播放的课程项会有一个.active类,下一节课的链接就在下一个li元素中。
// ==UserScript== // @name 视频自动连播助手 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 在指定学习平台自动播放下一节视频 // @author You // @match https://example-learning-site.com/course/* // @grant GM_notification // ==/UserScript== (function() { 'use strict'; // 配置项:可灵活修改 const CONFIG = { nextButtonSelector: '.course-sidebar .chapter-list li.active + li a', // 下一节课的CSS选择器 videoSelector: 'video', // 视频元素选择器 retryTimes: 3, // 元素查找重试次数 retryDelay: 1000, // 重试间隔(毫秒) enableNotification: true, // 是否启用通知 }; let currentRetryCount = 0; let observer = null; /** * 主初始化函数 */ function initAutoPlay() { console.log('[自动连播] 脚本启动,正在寻找视频元素...'); const video = findVideoElement(); if (video) { setupVideoListener(video); setupMutationObserver(); // 为动态加载的页面结构增加监听 } else { // 如果没立即找到视频,可能是页面加载慢,尝试轮询 retryFindVideo(); } } /** * 查找视频元素 */ function findVideoElement() { return document.querySelector(CONFIG.videoSelector); } /** * 轮询查找视频元素(备选方案) */ function retryFindVideo() { if (currentRetryCount >= CONFIG.retryTimes) { console.warn(`[自动连播] 重试 ${CONFIG.retryTimes} 次后仍未找到视频元素,脚本停止。`); return; } currentRetryCount++; console.log(`[自动连播] 第 ${currentRetryCount} 次尝试查找视频...`); setTimeout(() => { const video = findVideoElement(); if (video) { setupVideoListener(video); } else { retryFindVideo(); } }, CONFIG.retryDelay); } /** * 为视频元素设置播放结束监听 * @param {HTMLVideoElement} video */ function setupVideoListener(video) { // 先移除可能存在的旧监听器,避免重复绑定 video.removeEventListener('ended', handleVideoEnded); video.addEventListener('ended', handleVideoEnded); console.log('[自动连播] 视频结束监听器已绑定。'); if (CONFIG.enableNotification) { GM_notification({ text: '自动连播已就绪', title: '脚本提示', timeout: 2000 }); } } /** * 视频播放结束的处理函数 */ function handleVideoEnded() { console.log('[自动连播] 当前视频播放完毕,寻找下一节...'); const nextLink = document.querySelector(CONFIG.nextButtonSelector); if (nextLink) { console.log(`[自动连播] 找到下一节链接: ${nextLink.href}`); nextLink.click(); // 模拟点击 // 如果是SPA,点击后页面可能不刷新,需要在这里重新初始化监听 // 这里假设点击后页面会跳转,由新页面重新运行脚本 } else { console.log('[自动连播] 未找到下一节链接,可能已是最后一节。'); if (CONFIG.enableNotification) { GM_notification({ text: '已是最后一节课程,自动连播停止。', title: '脚本提示', timeout: 3000 }); } } } /** * 设置MutationObserver,监听DOM变化 * 用于应对视频播放器或课程列表是JS动态生成的情况 */ function setupMutationObserver() { // 如果已经存在观察者,先断开之前的连接 if (observer) { observer.disconnect(); } // 创建一个观察者实例,监听整个body子树的变化 observer = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { // 检查是否有新增节点,并且这些节点中包含视频元素 if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // 简单检查:如果新增节点中有video标签,或者整个播放器容器被替换了,就重新初始化 // 这里可以根据实际网站结构优化选择器 const video = findVideoElement(); if (video && !video.hasAttribute('data-autoplay-bound')) { // 给视频元素打上标记,避免重复绑定 video.setAttribute('data-autoplay-bound', 'true'); setupVideoListener(video); console.log('[自动连播] 检测到动态加载的视频,已重新绑定监听。'); } } } }); // 开始观察body元素,配置观察子节点的增减 observer.observe(document.body, { childList: true, subtree: true }); console.log('[自动连播] DOM变化监听器已启动。'); } // 页面加载完成后,延迟一小段时间再初始化,确保DOM基本就绪 window.addEventListener('load', () => { setTimeout(initAutoPlay, 500); }); // 同时,如果页面是SPA,通过history API监听路由变化(高级用法) // 这里提供一个基础示例,部分网站可能需要 const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { originalPushState.apply(this, args); window.dispatchEvent(new Event('locationchange')); }; history.replaceState = function (...args) { originalReplaceState.apply(this, args); window.dispatchEvent(new Event('locationchange')); }; window.addEventListener('popstate', () => { window.dispatchEvent(new Event('locationchange')); }); window.addEventListener('locationchange', () => { // 路由变化后,稍等片刻让新内容加载 setTimeout(initAutoPlay, 1000); }); })();代码关键点解析:
- 元信息头 (
==UserScript==): 这是Tampermonkey脚本的配置区。@match指定了脚本生效的网址模式,必须精确。@grant声明了需要使用的特殊API,比如GM_notification。 - 配置对象 (
CONFIG): 将所有可配置项集中管理,如选择器、重试次数。这样当网站改版时,你只需要修改这里的选择器字符串,而不用深入代码逻辑。 findVideoElement与retryFindVideo: 直接查询和轮询查询相结合。不是所有页面一打开视频元素就存在的,轮询是一个简单有效的容错机制。setupVideoListener: 绑定ended事件前,先移除旧监听器,这是一个好习惯,防止内存泄漏或重复绑定导致函数被执行多次。handleVideoEnded: 核心响应函数。这里使用了CONFIG.nextButtonSelector来查找元素。nextLink.click()是模拟点击的最直接方式。MutationObserver: 这是处理现代网页(单页应用、动态加载)的利器。它监听指定DOM节点内的变化,一旦发现新增节点中包含视频元素,就重新执行绑定。我们给绑定过的视频加了一个自定义属性>function waitForElement(selector, callback, maxAttempts = 10) { let attempts = 0; const interval = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(interval); callback(element); } else if (attempts >= maxAttempts) { clearInterval(interval); console.warn(`等待元素 ${selector} 超时。`); } attempts++; }, 500); } // 使用方式 waitForElement(CONFIG.videoSelector, (video) => { setupVideoListener(video); });- 监听特定事件: 有些网站会在内容加载完成后触发自定义事件,或者使用如
window.appReady这样的全局变量。这需要你观察网站的JavaScript执行逻辑。
4.3 应对网站反自动化策略
一些网站会检测异常的自动化操作,比如极快频率的点击、没有伴随鼠标移动事件的点击等。
- 添加随机延迟: 在关键操作(如点击下一集)前,用
setTimeout添加一个随机的、人性化的延迟(例如 1~3 秒)。function simulateHumanClick(element) { const delay = 1000 + Math.random() * 2000; // 1-3秒随机延迟 setTimeout(() => { element.click(); }, delay); } - 触发更多事件: 除了
click(),可以尝试模拟更完整的事件流。function advancedClick(element) { element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); element.dispatchEvent(new MouseEvent('click', { bubbles: true })); // 有时还需要focus事件 element.focus(); } - 降低检测频率: 避免使用
setInterval进行毫秒级的高频DOM查询,这很容易被识别为脚本行为。MutationObserver本身是异步的,频率较低,更隐蔽。
4.4 增加用户控制界面
一个成熟的脚本应该允许用户临时关闭或配置。Tampermonkey提供了GM_getValue和GM_setValueAPI来存储配置。你还可以在页面上注入一个小的控制面板。
// @grant 需添加 GM_getValue, GM_setValue // @grant GM_getValue // @grant GM_setValue function createControlPanel() { const panel = document.createElement('div'); panel.id = 'auto-play-control'; panel.style.cssText = 'position: fixed; bottom: 20px; right: 20px; background: #fff; border: 1px solid #ccc; padding: 10px; z-index: 9999;'; panel.innerHTML = ` <strong>自动连播控制</strong><br> <label><input type="checkbox" id="toggleAutoPlay"> 启用自动连播</label><br> <button id="nextNow">立即下一集</button> `; document.body.appendChild(panel); const toggle = panel.querySelector('#toggleAutoPlay'); toggle.checked = GM_getValue('enabled', true); // 读取存储的状态 toggle.addEventListener('change', (e) => { GM_setValue('enabled', e.target.checked); if (e.target.checked) { initAutoPlay(); } else { // 停止所有监听和轮询 if (observer) observer.disconnect(); console.log('[自动连播] 已手动关闭。'); } }); panel.querySelector('#nextNow').addEventListener('click', () => { // 手动触发下一集逻辑 const nextLink = document.querySelector(CONFIG.nextButtonSelector); if (nextLink) nextLink.click(); }); } // 在initAutoPlay开始时调用 createControlPanel(); if (GM_getValue('enabled', true)) { initAutoPlay(); }5. 常见问题排查与优化实录
在实际使用中,你肯定会遇到各种问题。下面是我总结的一些常见情况及其解决方案。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 脚本完全不运行 | 1.@match网址模式错误。2. Tampermonkey未启用或脚本未启用。 3. 脚本语法错误。 | 1. 检查浏览器地址栏URL是否匹配@match规则(支持通配符*)。2. 点击Tampermonkey图标,确保脚本是启用状态。 3. 打开浏览器控制台(F12 -> Console),查看是否有红色报错信息。 |
| 找到了视频,但结束后不切换 | 1.ended事件未触发(可能是流媒体或特殊播放器)。2. “下一集”按钮的选择器错误或失效。 3. click()模拟点击无效。 | 1. 尝试监听timeupdate事件,判断当前时间是否接近视频总时长。2. 在控制台手动执行 document.querySelector(CONFIG.nextButtonSelector),验证是否能找到元素。3. 尝试使用 advancedClick函数模拟更完整的事件,或直接使用window.location.href跳转。 |
| 切换到新页面后,脚本在新页面不生效 | 1. 新页面URL不在@match规则内。2. 新页面是SPA,未触发脚本重新初始化。 | 1. 调整@match,使其覆盖所有课程页面,如https://example.com/course/*。2. 实现并启用上文提到的SPA路由监听逻辑。 |
| 脚本运行一段时间后失效 | 1. 网站DOM结构更新,旧的选择器失效。 2. MutationObserver配置过于宽泛或狭窄,漏掉了变化。 | 1. 重新分析页面,更新CONFIG中的选择器。2. 调整 MutationObserver的observe配置,例如将subtree: true改为只观察特定容器。在回调函数中打印mutation,观察实际变化的是什么。 |
| 控制台出现大量错误或脚本导致页面卡顿 | 1. 轮询 (setInterval) 频率过高且未清除。2. MutationObserver回调函数执行了耗时操作。3. 内存泄漏(旧监听器未移除)。 | 1. 用setTimeout替代不间断的setInterval,或在找到元素后及时clearInterval。2. 在 MutationObserver回调中,使用debounce(防抖)技术,避免短时间内的多次变化触发多次处理。3. 确保在绑定新事件监听器前,移除旧监听器;在脚本停止或页面卸载时,断开 observer。 |
独家避坑技巧:
- “选择器沙盒”:在脚本开头定义一个测试模式。例如,设置
CONFIG.debug = true,当开启时,脚本不执行实际点击,只是在控制台打印它找到的元素和准备执行的操作。这能安全地验证你的逻辑是否正确。 - 日志分级:使用
console.log,console.warn,console.error区分不同重要性的信息。在脚本关键节点(如初始化、找到元素、触发点击)输出日志,方便追踪执行流。 - 版本管理与备份:网站经常改版。在Tampermonkey中编辑脚本时,先复制一份旧版本代码作为备份。修改选择器后,更新脚本的
@version号,这样你就能清楚知道哪个版本对应哪个网站结构。 - 社区力量:很多流行的网站已经有用户分享了现成的Tampermonkey脚本。在GreasyFork等脚本平台搜索一下,也许能找到灵感或直接可用的解决方案,你可以学习别人的代码来完善自己的脚本。
编写Tampermonkey脚本是一个与网站“斗智斗勇”的过程,充满了乐趣和挑战。它要求你细心观察、耐心调试,并不断适应变化。当你成功运行起一个自己编写的自动播放脚本,看着视频一集接一集无缝播放时,那种成就感是实实在在的。希望这份详细的指南能帮你少走弯路,顺利打造出属于你自己的浏览自动化工具。
