
1. 项目概述一个“技术性”的校园生存指南每年开学季对于刚踏入大学校园的新生来说除了新鲜和兴奋还有一堆“必修”的在线课程等着他们其中“新生安全教育”绝对是绕不开的一座大山。这门课内容通常涵盖消防安全、交通安全、防范诈骗、实验室安全等内容重要但形式往往比较枯燥动辄几十个视频章节每个视频看完还有答题环节全部手动完成耗时耗力。于是“如何快速完成”就成了一个在寝室里心照不宣的“技术需求”。我在这里要聊的不是鼓励大家跳过学习而是从一个技术实践者的角度拆解这个“需求”背后的技术实现逻辑。这本质上是一个浏览器自动化和Web交互模拟的问题。通过编写一小段JavaScript脚本我们可以让浏览器自动执行观看视频、点击下一节、自动答题等一系列重复操作。这不仅能节省大量时间更重要的是在这个过程中你能深入理解现代Web应用是如何运作的如何与页面元素交互以及如何编写稳健的自动化脚本。这对于计算机相关专业的学生来说是一次绝佳的、贴近实际需求的编程练习。2. 核心思路与技术选型解析2.1 需求拆解我们要让脚本做什么在动手写代码之前我们必须把“完成课程”这个模糊的目标拆解成一系列具体的、可程序化执行的步骤。通常一个典型的新生安全教育课程页面逻辑如下章节列表遍历课程由多个章节组成脚本需要能自动识别并进入下一个章节。视频播放检测每个章节的核心是一个视频。脚本需要能检测视频是否存在并触发播放。播放时长监控系统通常要求视频必须播放完毕或达到特定时长如95%才能进入下一步。脚本需要能监控视频的当前播放进度。答题逻辑处理视频播放完毕后通常会弹出测验题目。脚本需要能获取题目和选项并选择正确答案。状态判断与导航完成一个章节后判断是进入下一章还是课程已结束。2.2 为什么选择纯JavaScript浏览器控制台实现浏览器自动化常见的工具有Python的Selenium、PuppeteerNode.js等。但对于这个特定场景我强烈推荐直接使用浏览器自带的开发者工具中的控制台Console直接注入JavaScript代码。理由如下零环境依赖不需要安装任何额外的驱动、库或环境。任何现代浏览器Chrome, Edge, Firefox都自带开箱即用。直接作用于当前页面代码运行在目标课程网页的上下文中可以直接访问和操作页面所有的DOM元素和JavaScript对象没有跨域限制。适合快速原型与学习对于新手来说可以一边写代码一边直接在页面上看到效果即时调试学习曲线平缓。规避检测风险较低相比完整的自动化框架简单的控制台脚本行为更接近人工操作因为确实是在原浏览器标签页内执行触发网站反自动化机制的概率相对较小但并非为零后续会讲注意事项。注意此方法仅适用于学习和研究Web自动化技术原理。请务必遵守课程平台的使用规定确保完成学习内容的真实掌握安全知识关乎切身利益。2.3 核心Web API与技术点我们的脚本将主要依赖以下几个关键的Web APIDOM操作document.querySelector,document.querySelectorAll用于精准定位页面上的按钮、视频元素、题目文本等。事件触发element.click()用于模拟鼠标点击element.dispatchEvent(new Event(change))用于模拟选择框变化等。异步等待setTimeout,setInterval用于在操作之间加入延迟模拟人类操作节奏并等待页面元素加载或视频播放。媒体元素监控HTMLMediaElementvideo标签的currentTime,duration,ended等属性用于监控视频播放进度。3. 脚本核心模块设计与实现下面我将分模块构建一个相对健壮的脚本。请在你的课程页面打开开发者工具F12切换到Console标签页跟随步骤一起尝试。3.1 基础等待与查找函数在自动化操作中由于网络延迟或页面渲染元素可能不会立即出现。我们需要一个健壮的等待函数。// 工具函数等待直到某个元素在页面上出现 function waitForElement(selector, timeout 30000, interval 500) { return new Promise((resolve, reject) { const startTime Date.now(); const timer setInterval(() { const element document.querySelector(selector); if (element) { clearInterval(timer); resolve(element); } else if (Date.now() - startTime timeout) { clearInterval(timer); reject(new Error(等待元素超时: ${selector})); } }, interval); }); } // 工具函数等待指定时间毫秒 function sleep(ms) { return new Promise(resolve setTimeout(resolve, ms)); }代码解读waitForElement函数返回一个Promise它会每隔interval毫秒检查一次页面是否存在selector对应的元素一旦找到就成功返回该元素如果超过timeout毫秒还没找到则报错。sleep函数就是一个简单的延时函数。这两个是构建稳定自动化脚本的基石。3.2 视频播放与监控模块这是脚本的核心需要处理不同的视频播放器实现。async function handleVideoPlay() { console.log(正在处理视频播放...); // 尝试多种常见视频选择器 const videoSelectors [ video, .video-js video, iframe, // 有些视频是内嵌iframe [class*video] video, [id*video] ]; let videoElement null; for (const selector of videoSelectors) { const el document.querySelector(selector); if (el (el.tagName VIDEO || selector iframe)) { videoElement el; console.log(找到视频元素选择器: ${selector}); break; } } if (!videoElement) { console.warn(未找到标准视频元素尝试查找并点击可能的播放按钮。); // 可能是点击播放的图片或div const playButtons document.querySelectorAll([class*play], [id*play], [rolebutton]); for (let btn of playButtons) { if (btn.innerText.includes(播放) || btn.getAttribute(aria-label)?.includes(play)) { btn.click(); console.log(已点击通用播放按钮); await sleep(2000); // 点击后重新尝试查找video元素 videoElement document.querySelector(video); if (videoElement) break; } } if (!videoElement) { throw new Error(无法定位视频播放控件可能需要手动处理。); } } // 如果是iframe情况较复杂通常简单等待一段时间 if (videoElement.tagName IFRAME) { console.log(视频位于iframe中等待预设时长如60秒...); await sleep(60000); // 等待60秒假设视频长度 return; } // 对于标准的video元素 const video videoElement; // 确保视频播放 if (video.paused) { video.play().catch(e console.error(自动播放被阻止:, e)); // 如果播放被阻止尝试点击视频本身 video.click(); } console.log(视频总时长: ${video.duration.toFixed(2)} 秒); // 监控播放进度 return new Promise((resolve) { const checkInterval setInterval(() { const progress (video.currentTime / video.duration) * 100; console.log(播放进度: ${progress.toFixed(2)}%); // 判断完成的条件1. 视频已结束2. 播放进度超过95%应对一些进度条bug if (video.ended || (video.duration 0 progress 95)) { clearInterval(checkInterval); console.log(视频播放完成或达到进度要求。); resolve(); } }, 3000); // 每3秒检查一次进度 }); }实操心得 不同学校使用的课程平台五花八门视频播放器的实现差异很大。上述代码提供了多种查找策略。最关键的是progress 95这个条件很多平台并不需要100%播放完毕达到95%左右就能解锁下一环节这个阈值可以根据实际情况调整。3.3 自动答题逻辑模块答题环节是最复杂的因为题目和答案类型多变。async function handleQuiz() { console.log(正在处理测验题目...); await sleep(2000); // 等待题目区域稳定加载 // 1. 查找题目容器 - 需要根据实际页面调整选择器 const questionContainers document.querySelectorAll(.question-item, .quiz-item, [class*question], .topic); if (questionContainers.length 0) { console.log(未检测到题目可能无需答题或页面结构不同。); return; } console.log(检测到 ${questionContainers.length} 道题目); for (const container of questionContainers) { // 2. 提取题目文本 (用于简单日志复杂情况可能需要匹配答案库) const questionTextEl container.querySelector(.question-stem, .title, .stem); const questionText questionTextEl ? questionTextEl.innerText.trim().substring(0, 50) ... : 未知题目; console.log(处理题目: ${questionText}); // 3. 尝试查找已选择的正确答案很多平台直接显示正确答案 const correctAnswerSpans container.querySelectorAll(.correct-answer, .right-answer, [style*color:green], .answer[style*green]); if (correctAnswerSpans.length 0) { console.log( - 页面已显示正确答案尝试模拟选择); // 通常正确答案附近会有对应的单选按钮或复选框 for (const span of correctAnswerSpans) { // 向上查找包含输入框的父级元素 let parent span; for (let i 0; i 5; i) { // 向上查找几层 parent parent.parentElement; if (!parent) break; const input parent.querySelector(input[typeradio], input[typecheckbox]); if (input) { input.click(); console.log( - 已点击对应选项); break; } } } await sleep(500); continue; // 处理下一题 } // 4. 如果页面没有直接显示答案采用“选择第一个选项”或“全选”的保守策略 // 查找选项 const options container.querySelectorAll(input[typeradio], input[typecheckbox]); if (options.length 0) { console.log( - 未显示答案发现 ${options.length} 个选项采用保守策略); // 如果是单选题选择第一个如果是多选题选中所有有些平台允许全选得分 const isMultiple container.querySelector(input[typecheckbox]) ! null; if (isMultiple) { for (const opt of options) { if (!opt.checked) opt.click(); } console.log( - (多选) 已全选所有选项); } else { // 单选题尝试点击第一个未选中的 for (const opt of options) { if (!opt.checked) { opt.click(); console.log( - (单选) 已选择第一个选项); break; } } } await sleep(500); } else { console.warn( - 题目“${questionText}”未找到标准输入选项可能需要手动处理。); } } // 5. 查找并点击“提交”或“下一题”按钮 const submitButtons [ button:contains(提交答案), button:contains(下一题), button:contains(确认), .submit-btn, .next-btn ].map(sel { try { // 简单模拟jQuery的:contains return Array.from(document.querySelectorAll(button)).find(btn btn.innerText.includes(sel.split()[1])); } catch(e) { return null; } }).filter(btn btn); if (submitButtons.length 0) { submitButtons[0].click(); console.log(已点击提交按钮); await sleep(3000); // 等待提交结果和页面跳转 } }注意事项 自动答题模块的通用性最差严重依赖具体页面结构。上述代码展示了一种“混合策略”优先利用页面本身显示的正确答案信息进行精准选择如果不行则采用保守的默认选择策略。这并不能保证100%正确率但对于一些只需“完成”而非“满分”的课程通常足够。对于重要的考试切勿依赖此方法。3.4 主流程控制与导航最后我们需要一个主函数来串联所有步骤并控制章节间的导航。async function runSafetyCourseAutomation() { console.log( 新生安全教育课程自动化脚本启动 ); // 设置一个总超时防止脚本无限运行 const overallTimeout 1000 * 60 * 60; // 1小时 const startTime Date.now(); try { while (Date.now() - startTime overallTimeout) { console.log(\n--- 开始处理当前章节 ---); // 步骤1: 处理视频 try { await handleVideoPlay(); } catch (e) { console.error(处理视频时出错:, e.message); // 可能是非视频章节或页面结构不同尝试继续 } // 步骤2: 处理可能出现的测验 try { await handleQuiz(); } catch (e) { console.error(处理测验时出错:, e.message); } // 步骤3: 寻找并点击“下一章”或“继续”按钮 await sleep(2000); // 操作后等待 const nextButtons [ button:contains(下一节), button:contains(继续学习), button:contains(下一章), .next-chapter, .continue, .nav-next ].map(sel { const text sel.split()[1]; return Array.from(document.querySelectorAll(a, button)).find(el el.innerText.includes(text)); }).filter(el el); if (nextButtons.length 0) { console.log(找到导航按钮点击进入下一章节...); nextButtons[0].click(); // 等待新章节页面加载 await sleep(5000); } else { // 可能已经完成或者需要查找课程完成状态 const completionIndicator document.querySelector(.completed, .finished, [class*success]); if (completionIndicator) { console.log( 检测到课程完成标识自动化任务结束 ); break; } else { console.warn(未找到明确的下一章按钮或完成标识脚本暂停。请手动检查页面状态。); // 可以在这里加入一个长等待或者手动中断循环 await sleep(30000); // 等待30秒再看 // 如果30秒后还是没变化可以尝试刷新或停止 // break; // 或 location.reload(); } } } } catch (error) { console.error(自动化流程发生致命错误:, error); } console.log( 脚本执行完毕 ); } // 执行主函数 runSafetyCourseAutomation();4. 实战部署、调试与高级策略4.1 如何在实际页面中运行打开课程页面登录你的学习平台进入需要完成的第一章课程播放页面。打开开发者工具按F12键或右键点击页面选择“检查”。切换到Console标签页在这里你将输入和运行代码。分段粘贴与执行由于控制台对长脚本支持可能不佳建议将上述四个模块的代码按顺序工具函数 - 视频模块 - 答题模块 - 主流程分别粘贴到控制台并执行。确保前面的函数都已定义后最后执行runSafetyCourseAutomation()。观察日志脚本运行后控制台会打印详细的日志告诉你当前正在进行的操作、进度和遇到的问题。这是你调试的主要依据。4.2 常见问题排查与调试技巧即使有了通用脚本在实际操作中你几乎一定会遇到各种问题。下面是一个排查指南问题现象可能原因排查与解决方法脚本报错xxx is not defined函数未正确加载或执行顺序错误确保所有工具函数和模块代码已按顺序在控制台执行过。可以尝试在控制台输入函数名看是否有定义。找不到视频元素视频播放器结构特殊如使用Canvas、特殊播放器1. 使用开发者工具的“元素检查”Elements功能仔细查看视频区域对应的HTML标签和类名。2. 更新handleVideoPlay函数中的videoSelectors数组加入你发现的新选择器。3. 极端情况下可能只需要模拟等待一段时间绕过播放检测。答题全部错误答题逻辑与页面不匹配或平台有反作弊机制1.手动完成一两道题观察正确答案出现时页面DOM结构发生了什么变化类名、样式。据此修改handleQuiz函数中的选择器逻辑。2.建立简单答案映射如果题目固定可以手动建立一个题目关键词到答案索引的映射对象实现精准答题。无法点击“下一章”按钮按钮的类名或文本与脚本中定义的不符1. 检查按钮的实际文本注意可能包含空格或不可见字符。2. 使用document.querySelectorAll(button)列出所有按钮查看其innerText。3. 修改nextButtons数组中的匹配文本。页面卡住或脚本停止页面加载慢或出现了未预料的弹窗如提示框1. 适当增加sleep的等待时间。2. 在控制台检查是否有JavaScript错误。3.手动干预检查页面是否有需要确认的弹窗手动关闭后脚本可能可以继续。进度达到95%后不继续视频完成判断逻辑不准确调整handleVideoPlay函数中的完成条件例如将progress 95改为progress 98或直接监听video.ended事件。高级调试技巧 在控制台中除了运行脚本你还可以随时进行交互式调试document.querySelector(‘你的选择器’)测试能否找到元素。$0在Elements面板选中某个元素后在Console中输入$0可以快速引用该元素方便测试点击$0.click()或查看属性。在脚本中临时加入debugger;语句浏览器执行到此处时会自动暂停进入调试模式你可以查看当前所有变量状态。4.3 反自动化机制应对与伦理考量一些成熟的在线学习平台可能会部署简单的反自动化机制用户行为检测监测快速的、无规律的点击事件。应对在我们的脚本中所有关键操作之间都加入了sleep随机延时可以改进为随机时间间隔模拟人类操作的停顿和思考。鼠标移动轨迹高级检测会监控鼠标移动。应对控制台脚本无法模拟鼠标移动这是此方法的一个局限。但对于大多数校内课程平台检测通常不会如此严格。Cookie/LocalStorage 标记可能会在本地记录异常完成时间。应对无完美解决方案。这提醒我们脚本最好在正常学习时间段内运行避免在极短时间内“学完”数十小时课程。最重要的伦理提醒编写和运行这个脚本的最大价值在于学习浏览器自动化、DOM操作和JavaScript编程的技术本身。安全教育课程的内容如防火、防盗、防诈骗、实验室规范等与每一位同学的生命财产安全息息相关。我强烈建议你在脚本运行的同时或之后认真浏览或回看课程的核心内容。技术是工具用来解放生产力进行更有价值的创造而不是用来绕过对重要知识的学习。掌握这些安全知识才是你大学生活真正的“快速通关”保障。5. 脚本优化与扩展思路如果你对编程感兴趣这个基础脚本可以作为一个起点进行很多有趣的扩展配置化将不同平台的选择器如视频选择器、下一章按钮文本提取成一个配置对象方便切换不同课程。状态持久化利用localStorage保存当前进度即使页面刷新或意外关闭脚本可以从断点继续。更智能的答题结合OCR光学字符识别技术对题目截图并识别文字通过网络请求查询答案需注意合规性。封装为浏览器插件将脚本打包成Chrome插件提供图形界面如开始/暂停按钮、进度显示体验更佳。错误恢复机制使用try...catch包裹每个独立操作即使某个章节出错也能记录日志并尝试跳过继续后续章节。通过这个完整的项目你不仅“解决”了一个具体问题更深入实践了前端JavaScript、浏览器API、异步编程和调试技巧。这些技能远比单纯完成一门课程作业有价值得多。记住保持好奇心多动手尝试多阅读错误信息你会在解决一个个类似的小问题中快速成长。