
1. 从手动到自动评教这件事为什么值得“折腾”每到学期末高校里总会上演一出“评教大戏”。学生们被要求在教务系统里对每一位任课老师进行打分和评价。这事儿听起来简单但实际操作起来却是个不折不扣的“体力活”。想象一下一个学生要面对十几门课程每门课的评价体系可能包含几十个评分项和主观题全部填完少说也得花上半小时。更别提那些需要反复登录、页面卡顿、提交失败的“常规操作”了。对于学生来说这成了期末复习之外的又一重负担对于学校而言评教数据的回收率、有效性和真实性也常常因此大打折扣。于是“教务系统自动评教”这个想法就在这种背景下应运而生了。它本质上是一个自动化脚本旨在模拟人工操作自动完成登录教务系统、选择课程、填写评价表单、提交结果这一系列流程。这听起来有点像“外挂”但其初衷并非为了作弊或破坏规则而是为了解决一个真实存在的效率痛点。很多同学开发这类脚本最初的想法很简单把时间从重复、机械的点击和填写中解放出来去做更有价值的事情比如复习备考。当然这里必须强调任何自动化工具的使用都必须严格遵守学校的相关规定不得用于恶意刷分或干扰正常教学秩序。本文探讨的技术实现更多是作为一种编程学习和自动化流程设计的案例。从技术角度看实现一个自动评教脚本会涉及到几个核心领域网络请求的模拟HTTP/HTTPS、网页数据的解析HTML/DOM、表单的自动填充与提交以及可能遇到的验证码识别、登录状态维持等反自动化对抗措施。这不仅仅是一个简单的“按键精灵”它要求开发者对Web前端与后端的交互逻辑有清晰的理解。接下来我将以一个典型的、基于Python的自动化方案为例手把手拆解其实现原理、核心步骤以及那些“踩过才知道”的坑。2. 核心原理拆解浏览器在背后都干了些什么在动手写代码之前我们必须先搞清楚当我们在浏览器里手动评教时到底发生了什么。这个过程就是我们编写脚本需要精准模拟的“剧本”。2.1 会话Session与Cookie你的“临时身份证”当你第一次输入学号和密码登录教务系统时服务器验证通过后会生成一个唯一的“会话标识”Session ID并通过响应头中的Set-Cookie字段发送给你的浏览器。浏览器会保存这个Cookie。此后你在该网站内的每一次点击、跳转、提交表单浏览器都会自动在请求头中带上这个Cookie通常是Cookie: JSESSIONIDxxxxxx。服务器通过这个Cookie来识别“哦是刚才那个已经登录的用户”。这就是HTTP协议无状态特性下维持用户登录状态的核心机制。注意我们的自动化脚本必须完整地模拟这一过程。这意味着不能只模拟一次登录请求就完事必须在后续的所有请求中手动维护并携带这个关键的Cookie。在Python的requests库中使用Session对象可以非常方便地做到这一点它会自动处理Cookie的存储和发送。2.2 表单提交与CSRF令牌防止“跨站攻击”的盾牌现代教务系统为了安全几乎都会使用CSRF跨站请求伪造令牌。你会发现评教页面的表单里除了你看得见的评分选项和文本框往往还藏着一个或多个名为_csrf、csrf_token或类似名称的隐藏输入框input typehidden ...。它的值是一串随机字符串。这个令牌是服务器在生成表单页面时动态创建的。当你提交表单时必须将这个令牌原封不动地随表单数据一起提交。服务器会校验你提交的令牌是否与它之前发给你的那个匹配。如果不匹配请求会被直接拒绝。这有效防止了恶意网站伪造你的身份提交表单。对于自动化脚本来说这意味着一个关键步骤在提交评教数据前必须先访问评教页面从HTML源码中把这个隐藏的CSRF令牌值“挖”出来。我们通常使用像BeautifulSoup或lxml这样的HTML解析库来完成这项工作。2.3 请求的观察与分析开发者工具是“眼睛”所有上述信息——登录的API地址、提交评教的URL、需要提交的表单字段名、CSRF令牌的位置——都需要我们通过浏览器的开发者工具按F12打开来观察和获取。具体操作流程如下登录过程打开教务系统登录页在开发者工具的“网络”Network选项卡中勾选“保留日志”Preserve log。然后手动输入账号密码登录。在网络请求列表中你会看到登录时产生的一系列请求。找到那个POST类型的、看起来是提交登录信息的请求通常URL包含login、doLogin等关键词。点击这个请求查看其“标头”Headers和“负载”Payload。这里记录了请求的URL、方法POST、以及提交的表单数据如username你的学号password加密后的密码。评教过程登录后进入评教页面。同样观察网络请求找到加载评教表单的那个GET请求查看其响应内容找到CSRF令牌。然后手动选择评分、填写意见点击提交。再次观察网络请求找到提交评教的那个POST请求查看其提交的数据格式。你会发现数据通常是一个键值对集合键名对应前端的name属性值就是你选择的分数或填写的文字。只有通过这样细致的观察我们才能编写出能精准模拟人工操作的脚本。不同学校的教务系统如强智、清元优软、金智等接口和字段名可能完全不同但分析方法是通用的。3. 实战构建一个Python自动评教脚本的诞生基于以上原理我们可以开始构建脚本。这里我选择Python因为它拥有极其丰富和强大的网络请求与解析库。核心工具是requests处理HTTP请求和BeautifulSoup解析HTML。3.1 环境准备与基础架构首先安装必要的库pip install requests beautifulsoup4脚本的基础骨架如下import requests from bs4 import BeautifulSoup import time class AutoEvaluation: def __init__(self, base_url, username, password): self.base_url base_url.rstrip(/) # 教务系统基础URL如 http://jwxt.xxx.edu.cn self.username username self.password password self.session requests.Session() # 创建会话对象自动管理Cookie self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 }) # 设置一个常见的浏览器UA避免被简单屏蔽 def login(self): 模拟登录流程 # 1. 首先访问登录页可能获取登录所需的初始token或验证码信息 login_page_url f{self.base_url}/login.jsp resp self.session.get(login_page_url) # 这里可能需要解析页面中的隐藏token具体看学校系统 # soup BeautifulSoup(resp.text, html.parser) # token soup.find(input, {name: lt})[value] # 举例 # 2. 构造登录数据并提交 login_api_url f{self.base_url}/doLogin.action # 登录接口需根据实际情况修改 login_data { username: self.username, password: self.password, # lt: token, # 如果有的话 # execution: e1s1, # _eventId: submit } login_resp self.session.post(login_api_url, datalogin_data) # 3. 验证登录是否成功简单方法检查响应内容或后续访问个人页面 if 用户名或密码错误 in login_resp.text: print(登录失败请检查账号密码。) return False print(登录成功) return True def get_evaluation_list(self): 获取待评教课程列表 # 访问评教入口页面 eval_entry_url f{self.base_url}/evaluation/toEvaluation.action resp self.session.get(eval_entry_url) soup BeautifulSoup(resp.text, html.parser) # 解析课程列表通常在一个table或ul列表中 course_list [] # 假设课程链接在 class 为 eval-course 的a标签里 for link in soup.select(a.eval-course): course_name link.text.strip() course_url link[href] # 处理相对URL if course_url.startswith(/): course_url self.base_url course_url course_list.append({name: course_name, url: course_url}) print(f找到 {len(course_list)} 门待评教课程。) return course_list def evaluate_course(self, course_info): 对一门课程进行自动评教 course_url course_info[url] print(f开始评教课程{course_info[name]}) # 1. 访问课程评教页面获取表单和CSRF令牌 resp self.session.get(course_url) soup BeautifulSoup(resp.text, html.parser) # 查找CSRF令牌 csrf_token_elem soup.find(input, {name: _csrf}) # 名称可能不同 if not csrf_token_elem: # 尝试其他常见名称 csrf_token_elem soup.find(input, {name: csrfToken}) or soup.find(input, {name: token}) if csrf_token_elem: csrf_token csrf_token_elem[value] else: csrf_token print(警告未找到CSRF令牌可能评教失败。) # 2. 解析评教表单的所有评分项 # 假设评分项是radio inputname类似 score1, score2... form_data {} if csrf_token: form_data[_csrf] csrf_token # 模拟填写这里我们假设对所有评分项都选择最高分例如5分 # 你需要根据实际页面的input名称来修改 for i in range(1, 11): # 假设有10个评分项 radio_name fscore{i} radio_input soup.find(input, {name: radio_name, value: 5}) # 找值为5的选项 if radio_input: form_data[radio_name] 5 else: # 可能是下拉菜单或其他形式需要具体分析 pass # 填写主观评价可选 comment_box soup.find(textarea, {name: suggestion}) if comment_box: form_data[suggestion] 老师讲课认真内容丰富受益匪浅。 # 通用好评 # 3. 找到表单提交的URL和方式 form_elem soup.find(form, idevalForm) or soup.find(form) # 找到评教表单 if not form_elem: print(未找到评教表单跳过该课程。) return False submit_url form_elem.get(action) if submit_url.startswith(/): submit_url self.base_url submit_url elif not submit_url.startswith(http): # 处理相对路径 submit_url course_url.rsplit(/, 1)[0] / submit_url # 4. 提交评教数据 submit_resp self.session.post(submit_url, dataform_data) if 评教成功 in submit_resp.text or 提交成功 in submit_resp.text: print(f课程 [{course_info[name]}] 评教成功) return True else: print(f课程 [{course_info[name]}] 评教可能失败请检查。) # 可以保存响应文本用于调试 # with open(error.html, w, encodingutf-8) as f: # f.write(submit_resp.text) return False def run(self): 主运行流程 if not self.login(): return time.sleep(1) # 等待一下模拟人工操作间隔 courses self.get_evaluation_list() for course in courses: self.evaluate_course(course) time.sleep(2) # 每评一门课间隔2秒避免请求过快被服务器限制 if __name__ __main__: # 使用时替换为你的实际信息 BASE_URL http://jwxt.yourschool.edu.cn USERNAME 你的学号 PASSWORD 你的密码 evaluator AutoEvaluation(BASE_URL, USERNAME, PASSWORD) evaluator.run()这是一个高度简化的框架实际学校的系统要复杂得多。但它清晰地展示了核心流程创建会话 - 登录 - 获取课程列表 - 循环处理每门课程获取令牌、构造数据、提交。3.2 关键环节的深度剖析与适配上面的框架代码中有几个关键点需要根据实际情况进行深度适配这也是最容易出问题的地方。1. 密码的加密与编码很多教务系统不会明文传输密码。你需要在开发者工具中仔细观察登录请求的“负载”Payload。密码字段password的值可能是一串看似乱码的字符串。这通常是前端JavaScript进行了加密如MD5、SHA1、BASE64或自定义算法。我们的脚本必须模拟同样的加密过程。解决方法有两种方法A推荐找到前端加密的JS代码用Python实现相同的加密逻辑。这需要一定的JavaScript逆向能力。方法B取巧如果加密不是强随机性的比如只是简单的MD5你可以先手动登录一次从开发者工具中复制出加密后的密码字符串直接硬编码到脚本里。但注意如果密码加密使用了随机盐salt此方法失效。2. 动态加载的课程列表有些系统的评教课程列表不是直接写在HTML里而是通过Ajax异步加载的JSON格式。这时get_evaluation_list方法就不能再解析HTML了而需要找到那个加载列表数据的API接口通常在“网络”选项卡中看到一个返回JSON的XHR请求然后直接用session.get()或session.post()去请求这个接口解析返回的JSON数据。3. 复杂表单结构与评分逻辑评分表可能非常复杂有单选、多选、滑块、矩阵量表多个题目共用一套选项。你需要仔细分析每个评分项对应的HTML元素。例如矩阵量表中每个问题的选项可能name相同但value不同或者通过name中的下标来区分。构造form_data字典时必须确保键值对与浏览器提交的完全一致。一个有效的调试方法是先用脚本打印出解析到的所有input/select/textarea的name和value然后手动在浏览器提交一次对比网络请求中的表单数据查漏补缺。4. 验证码CAPTCHA的识别这是自动化最大的拦路虎。如果登录或评教环节有验证码脚本的复杂度将急剧上升。处理方案有人工介入在需要验证码时脚本暂停将验证码图片下载到本地或显示出来等待用户手动输入然后继续执行。OCR识别使用开源的OCR库如ddddocr、pytesseract进行识别但对付扭曲、干扰线多的验证码效果不佳。打码平台调用第三方付费打码API由人工平台协助识别可靠性高但有成本。绕过策略观察验证码是否在同一个会话中会重复使用或者是否有其他无需验证码的备用接口可能性极低。4. 进阶挑战与反自动化对抗策略当你以为脚本可以完美运行时很可能会遇到以下问题这都是教务系统为了阻止自动化操作而设置的“路障”。4.1 请求频率限制与行为检测服务器可能会监控异常请求。如果你的脚本以极快的速度比如每秒数次连续提交评教很可能会触发风控导致IP被临时封锁或会话失效。应对策略增加随机延迟在关键操作如登录后、访问每个课程页面前、提交后使用time.sleep()增加等待时间并且延迟时间最好在一定范围内随机如time.sleep(random.uniform(1, 3))模拟人的操作间隔。模拟更真实的浏览行为在访问评教页面之前可以先随机访问几个其他页面如成绩查询、课表页面让请求序列看起来更“自然”。使用代理IP池如果IP被封锁可以考虑使用代理IP。但对于个人评教而言通常不需要如此复杂。4.2 前端JavaScript动态计算参数有些系统会在提交前通过JavaScript动态计算一些参数并添加到表单中比如时间戳的哈希值、鼠标移动轨迹的加密值等。这些值单纯通过解析静态HTML是获取不到的。应对策略使用无头浏览器这是解决复杂动态网页自动化的终极方案。工具如Selenium、Playwright或Puppeteer可以控制一个真实的浏览器如Chrome来加载页面、执行JavaScript、模拟点击和输入。它能完美处理所有前端动态生成的内容。缺点是运行速度慢、资源占用高。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver webdriver.Chrome() driver.get(login_url) driver.find_element(By.ID, username).send_keys(username) driver.find_element(By.ID, password).send_keys(password) driver.find_element(By.ID, submitBtn).click() # ... 后续操作使用无头浏览器时同样需要注意延迟和避免检测有些网站会检测webdriver属性。4.3 登录状态丢失与异常处理网络波动、服务器错误、会话超时都可能导致脚本运行中断。一个健壮的脚本必须有完善的异常处理和状态检查。应对策略加入重试机制对于网络请求使用try...except包裹并在失败后重试几次。定期检查登录状态在评教几个课程后可以访问一下“个人中心”页面检查页面内容是否包含用户名以此判断会话是否依然有效。如果失效则需要重新调用login()方法。详细的日志记录将每一步操作、遇到的异常、服务器返回的关键信息都记录到日志文件或控制台便于出错时排查。5. 伦理、风险与最佳实践在技术实现的兴奋之余我们必须冷静地讨论使用自动评教脚本的边界。1. 遵守规则是第一前提务必仔细阅读你所在学校关于教务系统使用的管理规定。明确禁止自动化操作的系统切勿使用脚本以免触犯校规导致账号被封禁甚至更严重的处分。本文的技术分享仅用于学习和研究HTTP协议、Web自动化以及Python编程请勿用于任何违规用途。2. 评教数据的真实性与价值评教的根本目的是帮助老师改进教学、让学校了解教学情况。全部打满分或随意填写的自动化评教产出的数据是无效甚至有害的扭曲了真实的反馈。即使使用脚本也建议在关键的主观评价部分根据课程实际情况进行手动填写或者至少设置一个合理的、有波动的评分逻辑而不是千篇一律的满分。3. 脚本的保管与使用此类脚本包含了你的学号和密码即使经过加密处理。务必妥善保管代码不要上传到公开的代码仓库如GitHub以免个人信息泄露。可以在代码中通过读取环境变量或外部配置文件的方式来传入敏感信息。4. 一个折中的“半自动”方案如果你希望节省时间又希望保留一定的真实性可以考虑“半自动”方案让脚本自动完成登录、跳转到每门课的评教页面、并自动填充好所有固定的客观题评分例如都填“非常同意”然后暂停弹出主观题输入框让你手动填写意见或者让你快速检查一下客观题评分是否需要微调确认后再由脚本提交。这样既提升了效率又保证了核心反馈的真实性。实现这样一个自动评教脚本是一个绝佳的Web逆向工程和自动化实战项目。它迫使你去理解HTTP协议、会话管理、前端与后端的交互、反爬虫策略等一整套知识。无论最终你是否用它来“偷懒”这个构建过程本身带来的技术提升都是实实在在的。在动手的过程中你遇到的每一个错误、解决的每一个问题都是比单纯使用脚本更宝贵的经验。