尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于油猴脚本的正方教务系统教学评价自动化实现与逆向分析

基于油猴脚本的正方教务系统教学评价自动化实现与逆向分析 1. 项目概述从“手动填坑”到“一键清空”的进化又到了一学期末相信不少同学和我一样面对学校那个“经典永流传”的正方教务系统特别是那个教学评价环节总有种说不出的烦躁。页面卡顿、选项冗长、强制提交才能查成绩……这些“传统艺能”想必大家都不陌生。最近我发现我们学校的正方系统似乎进行了一次界面更新虽然底层逻辑可能还是那个味儿但前端的交互方式有了一些变化。这直接导致我之前写的一些用于简化评价流程的浏览器脚本油猴脚本失效了。作为一个“懒癌”晚期但追求效率的理工男我决定重新研究一下这个“新版”正方教务系统的教学评价模块写一个全新的、更稳定的一键评价脚本。这个项目的核心目标很简单在符合学校规定、完成必要评价任务的前提下最大限度地节省同学们的时间和精力。请注意这里的一键评价并非胡乱选择或恶意差评而是通常采用一种“默认好评”的策略即自动将所有评价项选择为最高或较高的分数如“非常同意”、“优秀”并自动填写统一的、正面的主观评价文字如“老师教学认真受益匪浅”最后自动提交。这本质上是一个前端自动化脚本它模拟了人工点击和填表的所有操作但速度是毫秒级的。适合谁来看这篇内容呢首先肯定是深受其苦的广大同学们其次是对Web前端自动化、浏览器扩展开发感兴趣的朋友这是一个非常贴近实际、能立刻看到效果的练手项目最后即便是你不打算自己写脚本了解其背后的原理也能让你更懂你每天打交道的系统甚至能举一反三应用到其他类似场景中。2. 逆向分析与思路拆解新瓶装旧酒的破解之道每次正方系统更新哪怕只是换了个按钮颜色都可能让旧的自动化脚本失效。因此我们的首要任务不是盲目写代码而是静下心来像侦探一样分析这个“新版”评价页面的运作机制。2.1 页面结构与数据流分析打开教学评价页面按下F12打开开发者工具这是我们的主战场。这次更新后我发现几个关键变化表单提交方式早期的版本可能使用简单的form表单POST提交现在更多采用XMLHttpRequest或Fetch API进行异步提交。在“网络”(Network)选项卡中进行一项评价并提交观察产生的网络请求。我抓取到的请求是一个POST请求地址类似于/jwglxt/xxxx/xxxEvaluationAction.do?methodsave。请求载荷Payload这是核心中的核心。点击提交后在刚才抓到的那个请求详情里查看“载荷”(Payload)或“请求体”(Request Body)。这里通常是一个x-www-form-urlencoded格式或FormData格式的数据。你会看到一串密密麻麻的参数例如evaluationId123456courseId789012teacherId345678item_15item_25...suggestText老师很好其中item_1、item_2等就对应着页面上每一个评分项如“教学态度”、“教学内容”等其值5可能代表“非常同意”假设是5分制。suggestText就是主观评价文本框的内容。动态参数与令牌Token为了防止重复提交或CSRF攻击系统通常会引入一个随机的令牌。这个令牌可能隐藏在页面的某个input typehidden标签里名字可能是csrfToken、_token或一个很长的随机字符串。在提交时这个令牌必须作为参数一并发送否则服务器会拒绝请求。这是新版系统最容易导致脚本失效的地方必须仔细查找并正确获取。元素选择器页面的HTML结构可能变了。之前脚本里用来定位“提交按钮”的CSS选择器如#btnSubmit可能已经失效。需要重新使用开发者工具的“元素检查”(Inspect)功能找到新页面中评分单选按钮、文本框、提交按钮的真实ID或Class。2.2 技术方案选型为什么是油猴脚本Tampermonkey实现前端自动化有多种方式比如浏览器插件、Selenium等。我坚持使用油猴脚本Tampermonkey有以下几个理由零部署、跨平台用户只需要安装一个油猴插件然后导入脚本即可。无需配置复杂的环境在Chrome、Edge、Firefox等主流浏览器上都能运行。精准作用于特定页面通过match或include元数据可以让脚本只在正方教务系统的评价页面运行不会干扰其他网站安全可控。开发调试便捷直接基于JavaScript和DOM操作可以利用浏览器原生的开发者工具进行调试所见即所得。社区生态丰富有大量的现成示例和文档可供参考遇到问题容易找到解决方案。注意任何自动化工具的使用都必须遵守目标网站的服务条款。本脚本仅用于学习Web自动化技术和提升个人效率请勿用于恶意刷评、攻击服务器或任何干扰正常教学秩序的行为。建议在完成自动填充后花几秒钟快速浏览一下确认无误后再由脚本提交做到心中有数。3. 脚本核心功能实现与代码解析基于上述分析我们来一步步构建脚本。一个健壮的脚本不仅要能“填”还要能“适应变化”。3.1 脚本元信息与框架首先是油猴脚本的标准头部信息它告诉油猴插件何时何地运行这个脚本。// UserScript // name 新版正方教务系统一键教学评价 // namespace http://tampermonkey.net/ // version 1.2 // description 自动填充新版正方教务系统教学评价表单并自动提交。请谨慎使用确认评价内容。 // author YourName // match *://*.your-school.edu.cn/jwglxt/xxxx/*evaluation*.do* // match *://jwxt.your-school.edu.cn/jwglxt/xxxx/*evaluation*.do* // grant none // /UserScript (function() { use strict; // 主逻辑将在这里编写 // 使用 setTimeout 或 DOMContentLoaded 事件确保页面完全加载 setTimeout(initScript, 1500); // 延迟1.5秒执行确保动态加载的元素就位 })();关键点解析match这是最重要的部分之一。你需要将其中的*.your-school.edu.cn替换成你们学校的真实域名。模式匹配要尽可能精确避免在其他页面误触发。setTimeout正方系统大量使用异步加载直接运行脚本可能找不到元素。延迟执行是一个简单有效的策略。更优的做法是监听特定元素出现的MutationObserver但为简化起见延迟足够长时间通常可行。3.2 核心自动化逻辑函数接下来是脚本的“大脑”——initScript函数。function initScript() { console.log(【一键评价脚本】开始执行...); // 1. 自动填充评分项假设是单选按钮 const ratingItems document.querySelectorAll(input[typeradio][value5]); // 选择所有值为5的radio代表最高分 if (ratingItems.length 0) { ratingItems.forEach(radio { radio.checked true; // 触发change事件确保某些依赖事件的JS能捕获到变化 radio.dispatchEvent(new Event(change, { bubbles: true })); }); console.log(已自动选择 ${ratingItems.length} 个最高分选项。); } else { // 如果radio选择器不工作尝试其他方式比如根据文字找父元素再点击 console.warn(未找到标准的评分单选按钮尝试备用方案...); fillRatingsByText(); } // 2. 自动填充主观评价文本框 const textarea document.querySelector(textarea[namesuggestText], textarea[id*suggest], textarea[class*suggest]); if (textarea) { textarea.value 老师教学认真负责课程内容充实让我受益匪浅。; // 默认评语 textarea.dispatchEvent(new Event(input, { bubbles: true })); // 触发input事件 console.log(已填充主观评价。); } // 3. 查找并获取CSRF Token等隐藏参数 let csrfToken ; const tokenInput document.querySelector(input[namecsrfToken], input[name_token], input[typehidden][value*]); if (tokenInput) { csrfToken tokenInput.value; console.log(获取到CSRF Token:, csrfToken); } // 4. 创建并插入“一键评价”按钮更友好的方式 createOneClickButton(csrfToken); }代码细节与避坑指南选择器策略querySelector和querySelectorAll非常强大。但正方系统的元素ID/Class可能毫无规律。除了name和id多尝试使用属性选择器[id*”eval”]id包含eval、[class*”radio”]等。事件触发仅仅设置radio.checked true可能不够因为页面的JS可能监听的是change或click事件。手动触发(dispatchEvent)这些事件可以更好地模拟真人操作。备用方案fillRatingsByText是一个假设的函数。如果直接选radio不行你可能需要遍历所有评价问题通常是tr或li然后根据其中的文字如“教学态度”找到对应的那组radio按钮再进行操作。这需要更精细的DOM遍历。3.3 构建更稳定的提交逻辑直接模拟点击页面的提交按钮是最简单的方式但不够稳健。更好的方式是直接模拟网络请求就像我们之前在开发者工具里看到的那样。function createOneClickButton(token) { // 防止重复添加按钮 if (document.getElementById(oneClickEvalBtn)) return; const btn document.createElement(button); btn.id oneClickEvalBtn; btn.innerHTML 一键好评并提交; btn.style.cssText position: fixed; top: 20px; right: 20px; padding: 10px 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer; z-index: 9999; box-shadow: 0 4px 6px rgba(0,0,0,0.1);; btn.addEventListener(click, function() { if (confirm(即将自动提交所有评价。请确认评价内容已自动填充完毕是否继续)) { submitEvaluationForm(token); } }); document.body.appendChild(btn); console.log(“一键评价”按钮已添加至页面右上角。); } function submitEvaluationForm(csrfToken) { // 这里需要根据实际抓包数据构造FormData const formData new FormData(); // 1. 收集所有评分项的值 document.querySelectorAll(input[typeradio]:checked).forEach((radio, index) { // 假设name是 item_1, item_2... formData.append(radio.name, radio.value); }); // 2. 收集文本框 const textarea document.querySelector(textarea[namesuggestText]); if (textarea) { formData.append(suggestText, textarea.value); } // 3. 添加必要的隐藏参数从页面中获取 const evaluationId document.querySelector(input[nameevaluationId])?.value; const courseId document.querySelector(input[namecourseId])?.value; if (evaluationId) formData.append(evaluationId, evaluationId); if (courseId) formData.append(courseId, courseId); if (csrfToken) formData.append(csrfToken, csrfToken); // 关键 // 4. 获取提交URL可以从页面表单的action属性获取或从抓包固定 const submitUrl document.querySelector(form[id*evalForm])?.action || /jwglxt/xxxx/xxxEvaluationAction.do?methodsave; // 5. 发送Fetch请求 fetch(submitUrl, { method: POST, body: formData, credentials: include // 携带Cookie保持登录状态 }) .then(response response.text()) .then(data { console.log(服务器响应:, data); // 简单判断成功与否根据响应内容 if (data.includes(成功) || data.includes(submitSuccess)) { alert(评价提交成功); // 可以尝试自动关闭标签页或跳转 // window.close(); // 可能需要浏览器授权 // window.location.href /jwglxt/xxxx/menu.do; } else { alert(提交可能失败请检查控制台响应信息。); } }) .catch(error { console.error(提交出错:, error); alert(网络请求失败: error.message); }); }重要提示fetch请求的URL和FormData的键值对必须与你抓包看到的一模一样。一个参数名错误或缺失特别是那个动态token都会导致提交失败。这就是逆向工程的核心。4. 实战调试与问题排查实录脚本写好了但一次成功是小概率事件。下面是我在调试过程中遇到的一些典型问题及解决方法这可能是比代码本身更有价值的部分。4.1 常见问题速查表问题现象可能原因排查步骤与解决方案脚本完全不运行1.matchURL模式不正确。2. 油猴插件未启用此脚本。3. 页面有iframe脚本注入到了错误上下文。1. 检查浏览器地址栏URL是否与match匹配。可先用*通配符测试。2. 点击油猴图标确保脚本已启用。3. 在开发者工具控制台输入GM_info看是否在油猴上下文中。对于iframe可能需要用grant GM_getResource等特殊处理。找不到元素选择器失效1. 页面元素是JS动态生成的。2. 元素ID/Class已变更。3. 脚本执行时机过早。1. 使用setTimeout增加延迟或使用MutationObserver监听DOM变化。2. 重新用开发者工具检查元素更新选择器。多用document.querySelectorAll并打印长度调试。3. 尝试在window.onload或更晚的事件中执行。评分已选但提交无效1. 仅改变checked属性未触发事件。2. 评分值未正确收集到表单数据中。1. 添加radio.click()或radio.dispatchEvent(new Event(change))。2. 提交前在控制台打印FormData或序列化的参数与手动提交抓包的数据对比确保一致。提交后服务器返回错误1. 缺少必填参数如evaluationId,csrfToken。2. 参数格式或编码错误。3. 请求头缺失如Content-Type。1.这是最关键的步骤。仔细对比脚本提交的请求和手动提交抓包的请求逐个参数核对。用console.log输出所有formData的键值。2. 使用new URLSearchParams(formData).toString()查看编码后的字符串。3.fetch默认会设置Content-Type但如果是x-www-form-urlencoded需明确设置headers: {‘Content-Type’: ‘application/x-www-form-urlencoded’}并用URLSearchParams作为body。“一键评价”按钮未出现1.createOneClickButton函数未被调用或报错。2. CSS样式导致按钮被遮挡。1. 检查控制台是否有JS错误。确保initScript被正确调用。2. 调整按钮的z-index样式为更大的值如99999。4.2 高级调试技巧深入网络请求当基础调试无效时需要更深入地介入网络层。拦截并修改请求可以使用油猴的grant GM_xmlhttpRequest或浏览器的Overrides功能开发者工具 - Sources - Overrides直接修改页面原始的提交JS代码或者拦截其请求查看和修改发出的数据。这需要较高的JS功底。请求重放Replay在开发者工具Network选项卡中找到手动提交成功的那个POST请求右键选择“Copy - Copy as fetch”。然后粘贴到脚本的fetch中只需替换其中动态变化的部分如评分值、token。这是最精准的方式。模拟登录状态确保你的脚本在正确的域名下运行并且浏览器已登录教务系统。fetch的credentials: ‘include’选项至关重要它保证了会话Cookie被发送。4.3 脚本的健壮性优化一个只能在自己电脑上运行的脚本不是好脚本。考虑到不同浏览器、学校服务器细微差异我们需要增加一些容错和兼容性处理。// 示例更健壮的元素查找和等待函数 function waitForElement(selector, timeout 5000) { return new Promise((resolve, reject) { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer new MutationObserver(() { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() { observer.disconnect(); reject(new Error(等待元素超时: ${selector})); }, timeout); }); } // 在initScript中使用 async function enhancedInit() { try { const submitButton await waitForElement(input[typesubmit], button[onclick*submit]); console.log(找到提交按钮页面加载完成); // 再执行填充逻辑 fillRatings(); fillTextarea(); createOneClickButton(); } catch (error) { console.warn(未找到特定元素尝试通用初始化:, error); // 通用初始化逻辑 setTimeout(initScript, 2000); } } // 替换原来的 setTimeout(initScript, 1500); // document.addEventListener(DOMContentLoaded, enhancedInit); // 或者使用DOMContentLoaded5. 安全、伦理与扩展思考在享受自动化便利的同时我们必须清醒地认识到边界。安全与合规性重申仅限个人学习与效率提升该脚本不应被用于批量注册、刷票、攻击服务器或任何形式的滥用。尊重评价本质教学评价本身是有价值的反馈机制。建议在脚本自动填充后快速浏览对确有感受的课程进行微调让自动化为你节省时间而非取代思考。防范风险不要在公共电脑或不信任的浏览器上使用此类脚本。理论上恶意脚本可以窃取你的cookie和会话信息。只使用来自可信来源的脚本。脚本的潜在扩展方向UI增强不只是加一个按钮可以嵌入一个浮动面板让用户自定义“默认评分”全优、全良、自定义评语模板甚至对不同的课程/老师设置不同的评价模板。状态保存与恢复利用GM_setValue和GM_getValue需申请grant权限保存用户上一次的评价习惯下次自动应用。批量处理如果系统允许一次性评价多门课程可以编写逻辑自动遍历课程列表依次完成评价。但这需要更复杂的页面状态管理和错误处理。错误重试与通知当网络提交失败时自动重试几次并在成功后通过浏览器通知Notification API告知用户。这个项目从表面看只是一个简单的“偷懒”工具但其背后涉及了对Web前端技术的深入理解DOM操作、事件循环、网络请求、异步编程、逆向工程。每一次正方系统的更新都是一次新的挑战和学习机会。最后我想说的是技术是用来解决问题的但解决问题的前提是理解问题、尊重规则。希望这个详细的拆解不仅能帮你搞定期末评价更能让你学到一些实实在在的、能举一反三的Web自动化知识。
返回列表