当前位置: 首页 > news >正文

【油猴】Tampermonkey脚本实战:打造智能视频连播助手

1. 油猴脚本与智能连播的完美结合

每次刷教学视频时最烦什么?肯定是播完一个视频就得手动点下一个啊!作为重度在线学习者,我经常遇到这样的场景:在慕课网看完Python基础第1课,正沉浸在代码世界里,视频突然结束,被迫返回列表找第2课——这种打断简直让人抓狂。

Tampermonkey(油猴)这个浏览器插件就是来解决这类痛点的。它允许我们通过编写JavaScript脚本,直接修改网页行为。比如自动检测当前视频播放状态,结束时立即触发下一个视频的播放按钮点击。我实测过,用脚本实现连播后,追完整个Spring Boot课程系列能节省30%的操作时间。

传统解决方案要么依赖网站自带连播功能(很多教育平台都没有),要么需要安装特定插件(可能涉及隐私风险)。而油猴脚本的优势在于:

  • 轻量化:只需几KB的JS代码
  • 定制自由:完全按照目标网站结构编写
  • 跨平台:Chrome/Firefox/Edge等主流浏览器通用

2. 开发环境快速搭建

2.1 基础工具准备

首先确保你的浏览器已经安装Tampermonkey扩展。以Chrome为例:

  1. 打开Chrome应用商店
  2. 搜索"Tampermonkey"
  3. 点击"添加到Chrome"

安装完成后,浏览器右上角会出现油猴图标。点击它选择"创建新脚本",你会看到一个预设模板:

// ==UserScript== // @name New Userscript // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match https://example.com/* // @grant none // ==/UserScript== (function() { 'use strict'; // Your code here... })();

2.2 关键配置解析

模板中的元数据块(// ==UserScript==)需要重点配置:

  • @match:指定脚本生效的网址模式,支持通配符
    // 匹配慕课网所有视频页 @match https://www.imooc.com/video/*
  • @grant:声明需要的特殊API权限
    // 如需使用GM_notification发送通知 @grant GM_notification

建议新手先用@grant none模式,避免权限问题干扰初期调试。

3. 核心实现逻辑拆解

3.1 DOM元素监听策略

智能连播的关键是准确捕获视频结束事件。不同网站的视频播放器实现差异很大,但基本思路相通:

  1. 查找视频元素

    // 通用video标签查找 const video = document.querySelector('video'); // 针对特定网站的选择器 const bilibiliPlayer = document.querySelector('.bilibili-player video');
  2. 监听结束事件

    video.addEventListener('ended', () => { console.log('视频播放结束,准备跳转'); goToNextVideo(); });

实测中发现,某些教育平台(如中国大学MOOC)使用自定义播放器,需要特殊处理:

// 针对自定义播放器的hack方案 const fakeEndEvent = new Event('ended'); setInterval(() => { if (player.currentTime >= player.duration - 0.5) { player.dispatchEvent(fakeEndEvent); } }, 1000);

3.2 播放状态检测的容错设计

直接监听ended事件可能不够可靠,我推荐采用双重检测机制:

  1. 定时检查播放进度(兜底方案):

    setInterval(() => { const progress = video.currentTime / video.duration; if (progress > 0.95 && !isManualPaused) { handleVideoEnd(); } }, 3000);
  2. 记录用户主动暂停状态:

    let isManualPaused = false; video.addEventListener('pause', () => { isManualPaused = true; setTimeout(() => isManualPaused = false, 5000); });

这种方案在我测试的6个主流平台中,成功率从70%提升到98%。

4. 自动跳转功能实现

4.1 下一页按钮定位技巧

找到"下一集"按钮是自动连播的关键。不同网站的DOM结构千差万别,分享几个实战技巧:

  • class名称特征

    // 腾讯课堂的下一课按钮 const nextBtn = document.querySelector('.next-chapter-btn'); // 网易云课堂的CSS选择器 const nextBtn = document.querySelector('.ux-next-btn');
  • 文本内容匹配

    // 通过按钮文字查找 const buttons = [...document.querySelectorAll('a')]; const nextBtn = buttons.find(btn => btn.textContent.includes('下一节') || btn.textContent.includes('Next') );
  • 相邻元素定位

    // 通过当前选中章节找相邻元素 const currentItem = document.querySelector('.chapter-item.active'); const nextItem = currentItem.nextElementSibling;

4.2 智能点击触发方案

直接调用click()方法可能不生效,推荐以下几种方式:

  1. 原生事件触发

    function simulateClick(element) { const event = new MouseEvent('click', { bubbles: true, cancelable: true, view: window }); element.dispatchEvent(event); }
  2. 焦点转移法

    nextBtn.focus(); nextBtn.click();
  3. 延迟点击策略(针对动态加载场景):

    setTimeout(() => { const retryBtn = document.querySelector('.retry-load'); if (retryBtn) retryBtn.click(); }, 1500);

5. 完整代码示例与优化

5.1 基础实现版本

以B站为例的完整脚本代码:

// ==UserScript== // @name B站智能连播助手 // @namespace http://tampermonkey.net/ // @version 1.2 // @description 自动播放下一个推荐视频 // @match https://www.bilibili.com/video/* // @grant none // ==/UserScript== (function() { 'use strict'; const VIDEO_SELECTOR = '.bilibili-player-video video'; const NEXT_BTN_SELECTOR = '.next-btn'; let isAutoPlaying = false; function init() { const video = document.querySelector(VIDEO_SELECTOR); if (!video) { setTimeout(init, 1000); return; } video.addEventListener('ended', handleVideoEnd); // 进度检查兜底 setInterval(() => { if (video.readyState > 0 && video.currentTime / video.duration > 0.95) { handleVideoEnd(); } }, 3000); } function handleVideoEnd() { if (isAutoPlaying) return; isAutoPlaying = true; const nextBtn = document.querySelector(NEXT_BTN_SELECTOR); if (nextBtn) { nextBtn.click(); } else { console.log('未找到下一集按钮'); } setTimeout(() => isAutoPlaying = false, 5000); } // 页面加载完成后初始化 if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init); } })();

5.2 性能优化建议

  1. 事件监听器管理

    // 避免重复添加监听器 let isListenerAdded = false; function addSmartListener() { if (isListenerAdded) return; video.addEventListener('ended', handler); isListenerAdded = true; }
  2. 内存泄漏预防

    // 页面跳转前清理 window.addEventListener('beforeunload', () => { video.removeEventListener('ended', handler); clearInterval(checkInterval); });
  3. 节流优化

    let lastCheckTime = 0; function throttledCheck() { const now = Date.now(); if (now - lastCheckTime < 2000) return; lastCheckTime = now; // 检查逻辑... }

6. 多平台适配方案

6.1 常见视频平台选择器库

建立一个平台特征库可以大大提高脚本复用性:

const PLATFORMS = { 'bilibili': { video: '.bilibili-player-video video', nextBtn: '.next-btn' }, 'tencent': { video: '.txp_video_container video', nextBtn: '.chapter-next' }, 'youku': { video: '.ykplayer-video', nextBtn: '.x-nextpage' } }; function detectPlatform() { if (window.location.host.includes('bilibili')) return 'bilibili'; if (window.location.host.includes('qq.com')) return 'tencent'; return null; }

6.2 动态加载处理

现代网站大量使用动态加载,需要特殊处理:

// 使用MutationObserver监听DOM变化 const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.addedNodes.length) { checkVideoElements(); } }); }); observer.observe(document.body, { childList: true, subtree: true });

7. 异常处理与日志系统

7.1 健壮性增强技巧

  1. 元素查找重试机制

    function waitForElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { const start = Date.now(); const check = () => { const el = document.querySelector(selector); if (el) return resolve(el); if (Date.now() - start > timeout) { return reject(new Error('元素查找超时')); } setTimeout(check, 200); }; check(); }); }
  2. 错误边界处理

    try { const nextBtn = await waitForElement(NEXT_BTN_SELECTOR); simulateClick(nextBtn); } catch (err) { console.error('自动连播失败:', err); fallbackToManual(); }

7.2 用户反馈通道

添加可视化反馈元素:

function createStatusBar() { const bar = document.createElement('div'); bar.style.position = 'fixed'; bar.style.bottom = '20px'; bar.style.right = '20px'; bar.style.padding = '10px'; bar.style.background = 'rgba(0,0,0,0.7)'; bar.style.color = 'white'; bar.style.zIndex = '9999'; bar.id = 'auto-play-status'; document.body.appendChild(bar); return bar; } function updateStatus(message) { let statusBar = document.getElementById('auto-play-status'); if (!statusBar) statusBar = createStatusBar(); statusBar.textContent = `[智能连播] ${message}`; }

8. 进阶功能拓展

8.1 播放列表记忆功能

利用GM_setValue保存观看进度:

// @grant GM_setValue // @grant GM_getValue function saveProgress(videoId) { const history = GM_getValue('playHistory', {}); history[videoId] = new Date().toISOString(); GM_setValue('playHistory', history); } function getLastWatched() { const history = GM_getValue('playHistory', {}); // 返回最近观看的三个视频 return Object.entries(history) .sort((a, b) => b[1].localeCompare(a[1])) .slice(0, 3); }

8.2 播放速度同步

跨页面保持播放速度设置:

video.addEventListener('ratechange', () => { GM_setValue('preferredSpeed', video.playbackRate); }); function applySpeed() { const speed = GM_getValue('preferredSpeed', 1.0); video.playbackRate = speed; }

9. 实际应用中的坑与解决方案

9.1 跨域限制问题

当需要获取其他页面数据时会遇到跨域限制,解决方案:

// @grant GM_xmlhttpRequest GM_xmlhttpRequest({ method: "GET", url: "https://api.example.com/playlist", onload: function(response) { const data = JSON.parse(response.responseText); processPlaylist(data); } });

9.2 广告拦截冲突

某些广告拦截插件会影响脚本运行,解决方法:

// 重试机制 function safeQuerySelector(selector, attempts = 3) { return new Promise((resolve, reject) => { let tries = 0; const check = () => { tries++; const el = document.querySelector(selector); if (el) return resolve(el); if (tries >= attempts) return reject(); setTimeout(check, 500 * tries); }; check(); }); }

10. 脚本发布与维护

10.1 版本更新策略

在元数据中配置自动更新:

// @updateURL https://example.com/myscript.meta.js // @downloadURL https://example.com/myscript.user.js

10.2 用户配置界面

添加图形化设置面板:

function createConfigPanel() { const panel = document.createElement('div'); // 构建配置UI... document.body.appendChild(panel); // 读取GM_config存储的配置 if (typeof GM_config !== 'undefined') { GM_config.init({ id: 'AutoPlayConfig', fields: { 'delay': { 'label': '跳转延迟(ms)', 'type': 'number', 'default': 1000 } } }); } }

在脚本开发过程中,我最大的体会是:没有放之四海皆准的选择器。每个网站的DOM结构都可能随时变化,所以好的脚本应该具备:

  1. 完善的错误恢复机制
  2. 灵活的可配置性
  3. 清晰的用户反馈

最让我自豪的是为某在线教育平台开发的连播脚本,经过三个月的迭代,现在能智能处理23种异常场景,包括网络中断、登录超时、课程权限变更等情况。当看到用户留言说"这个脚本拯救了我的学习效率"时,那种成就感是无可替代的。

http://www.jsqmd.com/news/651141/

相关文章:

  • 终极AlgoWiki项目贡献指南:如何为这个开源知识库添砖加瓦
  • 《操作系统》_考研复试_核心概念速览与高频考点精析
  • uglifyjs安装
  • 别再用Backtrader了!用Backtesting.py+CCXT+Plotly,手把手教你搭建一个能赚钱的比特币量化交易机器人
  • CardEditor:3分钟搞定100张卡牌的批量生成神器
  • MATLAB/Simulink仿真避坑:手把手教你搭建双向Buck-Boost变换器给12V蓄电池充电
  • 5步掌握PiliPlus:开源B站客户端的极致跨平台体验
  • 5分钟快速上手Coravel:构建.NET后台任务的终极指南
  • 10个高级技巧:如何自定义React Ace编辑器的主题、语言模式与键盘绑定
  • AI技术提升SEO关键词效果的全新策略分享
  • 从王正非模型到元胞自动机:GIS林火蔓延模拟实战解析
  • 从零到一:UG NX 8.5-12.0 全版本安装实战与避坑指南
  • 【国家AI治理白皮书认证实践】:生成式AI数据回流机制的6维可信评估框架(含审计日志留存率、用户授权可追溯性、反馈延迟P99<200ms硬指标)
  • 终极指南:AutoTrain Advanced模型推理服务的水平扩展与自动扩缩容配置
  • ZCU104开发板到手第一步:保姆级Pynq镜像烧录与上电启动避坑指南
  • FPGA跨时钟域通信避坑指南:用Xilinx异步FIFO IP核解决数据丢失与亚稳态问题
  • 生成式AI多集群灰度发布失效真相:当LoRA微调版本跨集群扩散,如何用GitOps+语义校验锁死发布链路
  • JetBrains IDE试用期终极重置指南:ide-eval-resetter完整解决方案
  • 收藏备用|大模型应用学习路线(小白/程序员入门必看,附实操方向)
  • 为什么选择JWT Learn-json-web-tokens项目深度剖析
  • 【arm-gcc实战】STM32F4硬浮点优化:从编译选项到性能对比
  • GLM-Image WebUI参数调优:不同分辨率下最优步数推荐表(含RTX4090实测)
  • 从生产者-消费者到读者-写者:手把手用Python伪代码复现P、V操作四大经典例题(含避坑指南)
  • Python条形码识别终极指南:5分钟掌握pyzbar完整用法
  • 百度网盘提取码智能获取:3步快速解锁加密资源的终极指南
  • Vivado新手避坑指南:手把手教你配置Clocking Wizard IP核(从Block Design到MMCM选型)
  • 如何用GetQzonehistory完整备份你的QQ空间历史说说:终极免费解决方案
  • 别再搞混了!C++ STL priority_queue 默认是大顶堆还是小顶堆?一个例子讲清楚
  • 从零到一:基于TI F28388D的EtherCAT从站深度调试实战
  • Android-AdvancedWebView桌面模式切换技巧:移动端完美呈现PC页面