ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python Selenium自动化评教脚本:从原理到实战,解放教务系统操作

Python Selenium自动化评教脚本:从原理到实战,解放教务系统操作 1. 项目概述当评教成为负担自动化是解放双手的钥匙每到学期末除了紧张的考试还有一项让无数大学生头疼的“例行公事”——教务系统评教。面对几十门课程每门课程几十个评价项重复点击、勾选、提交整个过程枯燥、耗时且往往流于形式。更让人无奈的是很多学校的评教系统界面老旧、响应缓慢手动操作不仅考验耐心还可能在提交时因网络波动或系统卡顿而前功尽弃。这个名为“教务系统自动评教”的项目正是为了解决这一痛点而生。它本质上是一个基于Python和Selenium的浏览器自动化脚本旨在模拟真人操作自动完成登录教务系统、遍历待评课程、填写评价问卷并提交的全过程。对于需要批量处理评教任务的学生或者希望研究自动化流程的开发者而言这个项目提供了一个清晰、可复现的技术实现路径。它不仅仅是“偷懒”的工具更是理解Web自动化、处理Web表单、应对简单验证码等实际开发场景的绝佳练手项目。2. 核心思路与技术选型为什么是Selenium在决定自动化评教时我们面临几个核心选择是直接模拟HTTP请求如使用requests库还是操控浏览器是否需要处理验证码如何定位页面元素2.1 操控浏览器 vs. 模拟请求直接模拟HTTP请求抓包分析接口效率最高对服务器压力小。但教务系统往往结构复杂涉及多次页面跳转、动态生成的令牌如__VIEWSTATE、__EVENTVALIDATION在ASP.NET中、会话Session维持以及可能存在的JavaScript动态渲染内容。逆向分析这些接口耗时巨大且系统一旦升级接口结构变化脚本就需要大幅调整维护成本高。相比之下通过Selenium等工具驱动真实浏览器如Chrome、Edge进行自动化操作优势明显所见即所得完全模拟用户操作无需关心背后复杂的网络请求和页面渲染逻辑。脚本的逻辑就是“找到输入框输入文字找到单选按钮点击它”。强鲁棒性只要网页前端显示正常脚本就能操作。对于采用前端框架如Vue, React或大量使用AJAX加载数据的现代教务系统Selenium能轻松应对。低分析成本省去了抓包、分析接口、构造参数的大量时间开发速度更快。因此对于评教这种以表单提交为主、业务逻辑相对透明在页面上可见的场景Selenium是更稳妥、更通用的选择。它的缺点主要是执行速度相对较慢以及需要额外的浏览器驱动但对于评教这种非高频任务这点开销完全可以接受。2.2 Selenium与相关生态Selenium是一个用于Web应用程序测试的自动化工具套件但我们完全可以将其用于合法的自动化操作。其核心是WebDriver这是一个控制浏览器的通用接口。我们需要Selenium库通过pip install selenium安装的Python客户端库用于编写控制脚本。浏览器驱动如ChromeDriver对应Chrome/Edge、GeckoDriver对应Firefox。驱动版本必须与本地安装的浏览器版本匹配这是新手最容易踩的坑。2.3 验证码的应对策略部分教务系统在登录时会加入验证码CAPTCHA以防止自动化。这是自动化脚本最大的障碍。我们的策略是分级处理简单图形验证码如果验证码是简单的数字、字母扭曲且颜色对比明显可以考虑使用OCR库如pytesseract配合Pillow进行图像处理进行识别。但成功率无法保证且增加了复杂度。复杂验证码或滑动验证对于这类验证在合法合规且仅用于个人学习的前提下更务实的做法是手动干预。我们可以在脚本中设计一个“暂停点”当运行到登录页面弹出验证码时脚本暂停等待用户手动识别并输入验证码然后脚本继续执行后续的评教操作。这实现了“半自动化”平衡了难度与实用性。最佳实践在编写脚本前先手动走一遍评教流程确认验证码出现的位置和类型。如果验证码难以自动化破解就将“手动输入验证码”作为脚本设计的一部分。3. 环境准备与核心工具搭建工欲善其事必先利其器。一个稳定的环境是脚本成功运行的基础。3.1 Python环境与Selenium安装建议使用Anaconda或Miniconda来管理Python环境避免包冲突。# 创建一个新的虚拟环境可选但推荐 conda create -n auto_evaluation python3.9 conda activate auto_evaluation # 安装selenium pip install selenium选择Python 3.7及以上版本即可确保稳定性。3.2 浏览器驱动的配置以最常用的Chrome浏览器为例查看本机Chrome版本打开Chrome点击右上角三个点 - 帮助 - 关于Google Chrome。访问ChromeDriver官方下载站或国内镜像站下载与你的Chrome浏览器主版本号完全相同的驱动文件例如Chrome是120.0.6099.110就下载120.x.x.x版本的ChromeDriver。将下载的chromedriver.exe文件放置在一个合适的位置。通常有三种方法方法一推荐将其放在Python解释器所在的目录或Scripts目录下因为该目录通常已在系统PATH环境变量中。方法二将其放在项目根目录下在代码中指定绝对路径。方法三将其所在目录添加到系统的PATH环境变量中。注意驱动与浏览器版本不匹配是导致SessionNotCreatedException错误的最常见原因。务必确保版本对应。3.3 IDE选择使用任何你熟悉的代码编辑器如VS Code、PyCharm均可。VS Code需要安装Python扩展并配置好刚才创建的虚拟环境。4. 脚本核心逻辑与代码逐行解析接下来我们构建一个完整的、具有容错能力的自动评教脚本。假设我们的教务系统是典型的列表-表单结构登录后有一个“待评教课程”列表点击每门课程进入一个评价表单页面表单提交后返回列表继续下一门。4.1 基础框架与浏览器启动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 from selenium.common.exceptions import TimeoutException, NoSuchElementException import time import random class AutoEvaluation: def __init__(self, username, password): self.username username self.password password # 初始化浏览器驱动这里以Chrome为例 options webdriver.ChromeOptions() # 可选添加参数使浏览器在后台运行无头模式调试时不建议开启 # options.add_argument(--headless) # 可选禁用GPU加速避免一些潜在问题 options.add_argument(--disable-gpu) # 禁用浏览器正在被自动化程序控制的提示 options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) self.driver webdriver.Chrome(optionsoptions) # 如果驱动不在PATH需指定executable_path参数 self.wait WebDriverWait(self.driver, 10) # 设置显式等待超时时间为10秒 self.driver.maximize_window() # 最大化窗口确保元素可见 def login(self, login_url): 登录教务系统 print(f正在访问登录页面: {login_url}) self.driver.get(login_url) # 等待登录表单加载完成这里假设有id为‘username’和‘password’的输入框 try: username_input self.wait.until( EC.presence_of_element_located((By.ID, username)) ) password_input self.driver.find_element(By.ID, password) login_button self.driver.find_element(By.ID, loginBtn) # 假设登录按钮ID except TimeoutException: print(错误未在页面上找到登录表单元素请检查页面结构或选择器。) self.driver.save_screenshot(login_page_error.png) # 截图保存以便调试 return False # 输入凭据 username_input.clear() username_input.send_keys(self.username) time.sleep(random.uniform(0.5, 1.5)) # 随机等待模拟人工操作间隔 password_input.clear() password_input.send_keys(self.password) time.sleep(random.uniform(0.5, 1.5)) # 关键验证码处理点 # 假设验证码图片id为‘captchaImg’输入框id为‘captcha’ try: captcha_img self.driver.find_element(By.ID, captchaImg) if captcha_img: print(检测到验证码请手动查看浏览器并输入验证码。) captcha_input self.driver.find_element(By.ID, captcha) # 脚本暂停等待用户手动输入 input(请在浏览器中输入验证码后回到此处按回车键继续...) except NoSuchElementException: print(未检测到验证码继续执行。) # 点击登录 login_button.click() print(已点击登录按钮。) # 等待登录成功后的页面跳转这里假设登录成功后会出现一个特定的元素例如id为‘welcome’的欢迎语 try: self.wait.until(EC.presence_of_element_located((By.ID, welcome))) print(登录成功) return True except TimeoutException: print(登录可能失败或跳转后的页面元素未找到。) # 检查当前URL或页面内容判断是否登录成功 if main in self.driver.current_url or 首页 in self.driver.page_source: print(通过URL或页面内容判断登录成功。) return True else: self.driver.save_screenshot(login_fail.png) return False代码解析与注意事项显式等待WebDriverWait配合expected_conditions是Selenium最佳实践之一。它让脚本等待特定条件成立如元素出现、可点击而不是使用固定的time.sleep这样更高效、更稳定。超时时间这里10秒可根据网络情况调整。随机等待在关键操作如输入、点击之间插入time.sleep(random.uniform(a, b))可以模拟人类操作的不确定性避免被简单的反爬机制识别为机器人。验证码处理代码中预留了手动输入验证码的交互。这是面对复杂验证时最可靠的方式。更高级的做法可以尝试OCR但需评估投入产出比。错误处理与调试使用try...except捕获异常并在出错时保存截图save_screenshot这是调试自动化脚本的利器能直观看到出错时的页面状态。4.2 定位待评课程列表并遍历登录成功后我们需要进入评教模块并获取所有待评课程的链接。def navigate_to_evaluation(self): 导航到评教页面 # 方式一如果评教有明确的链接直接点击 # eval_link self.wait.until(EC.element_to_be_clickable((By.LINK_TEXT, 教学质量评价))) # eval_link.click() # 方式二更常见的是需要从菜单或导航栏进入这里假设通过URL访问 evaluation_url http://your-jwxt.com/student/teachingEvaluation/toEvaluateList # 替换为实际URL print(f正在跳转到评教列表页面: {evaluation_url}) self.driver.get(evaluation_url) # 等待列表加载 try: self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, course-list))) # 假设列表容器类名 print(已进入评教列表页面。) return True except TimeoutException: print(评教列表页面加载失败。) return False def get_course_links(self): 获取所有待评教课程的链接或按钮 course_links [] # 假设每个课程项在一个class为‘course-item’的div中且评价按钮是里面的一个a标签或button # 使用更通用的XPath或CSS选择器 course_items self.driver.find_elements(By.CSS_SELECTOR, .course-list .course-item a.eval-btn) # 或者用XPath: //div[classcourse-list]//div[contains(class,course-item)]//a[text()评价] if not course_items: # 尝试其他选择器 course_items self.driver.find_elements(By.XPATH, //button[contains(text(),评价) or contains(text(),进入评价)]) for item in course_items: # 获取课程名称用于后续日志输出 # 可能需要根据实际页面结构向上查找父元素获取课程名 # course_name item.find_element(By.XPATH, ./../preceding-sibling::td[1]).text course_name item.get_attribute(title) or item.text[:20] # 简单处理 link_or_element item # 可能是WebElement不是href course_links.append((course_name, link_or_element)) print(f共找到 {len(course_links)} 门待评课程。) return course_links实操心得元素定位这是Selenium自动化中最关键也最繁琐的一步。不要依赖浏览器开发者工具直接复制的XPath它们可能过长且脆弱如包含大量div[1]/div[2]这样的索引。应优先使用ID、name其次是用class、tag组合的CSS选择器最后才是XPath。XPath应尽量使用属性匹配如id,class,contains(class, ‘partial’)而非位置索引。动态内容如果课程列表是滚动加载懒加载则需要模拟滚动操作driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”)来触发加载更多项目。4.3 自动填写评价表单并提交这是核心操作需要根据具体的评价表单结构来编写。def evaluate_single_course(self, course_info): 评价单门课程 course_name, eval_element course_info print(f开始评价课程: {course_name}) # 点击进入评价页面 try: # 如果eval_element是链接a标签可能有href可以直接get # 但更多情况是点击按钮触发JS所以用click eval_element.click() # 等待评价表单页面加载 self.wait.until(EC.presence_of_element_located((By.ID, evaluationForm))) # 假设表单ID except Exception as e: print(f进入课程 [{course_name}] 评价页面失败: {e}) return False # 开始填写表单 # 假设评价项都是单选按钮name相同value不同代表不同等级如1-5分 # 例如input typeradio namequestion1 value5 优秀 try: # 找到所有单选按钮组 # 通过查找所有唯一的name属性来定位问题 all_radio_inputs self.driver.find_elements(By.CSS_SELECTOR, input[typeradio]) radio_groups {} for radio in all_radio_inputs: name radio.get_attribute(name) if name and name not in radio_groups: radio_groups[name] self.driver.find_elements(By.NAME, name) print(f该课程共有 {len(radio_groups)} 个评价问题单选。) # 对每个问题随机选择一个选项或者按照预设逻辑选择如全部选最优 for group_name, radios in radio_groups.items(): if radios: # 策略1: 随机选择模拟不同评价 # selected_radio random.choice(radios) # 策略2: 固定选择最高分value最大的假设value是数字字符串 selected_radio max(radios, keylambda r: int(r.get_attribute(value) or 0)) # 滚动到元素可见 self.driver.execute_script(arguments[0].scrollIntoView({block: center});, selected_radio) time.sleep(random.uniform(0.2, 0.5)) selected_radio.click() time.sleep(random.uniform(0.1, 0.3)) # 点击后短暂等待 # 处理可能的文本评价框非必填 text_areas self.driver.find_elements(By.CSS_SELECTOR, textarea) for textarea in text_areas: # 可以留空或者输入一些通用评价 if random.choice([True, False]): # 随机决定是否填写 suggestion_texts [老师讲课认真受益匪浅。, 课程内容充实互动良好。, 无] textarea.send_keys(random.choice(suggestion_texts)) time.sleep(random.uniform(0.5, 1)) # 提交评价 submit_button self.wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, input[typesubmit], button[typesubmit])) ) self.driver.execute_script(arguments[0].scrollIntoView();, submit_button) time.sleep(random.uniform(0.5, 1)) submit_button.click() print(f课程 [{course_name}] 评价已提交等待结果...) # 等待提交成功提示或页面跳转 time.sleep(2) # 等待跳转 # 可以检查是否有成功提示例如包含“成功”、“感谢”等文字的弹窗或元素 success_indicator EC.presence_of_element_located((By.XPATH, //*[contains(text(), 成功) or contains(text(), 完成)])) try: WebDriverWait(self.driver, 5).until(success_indicator) print(f课程 [{course_name}] 评价成功) except TimeoutException: # 没有明确成功提示但可能已跳转回列表页 print(f课程 [{course_name}] 提交完成未检测到明确成功提示。) return True except Exception as e: print(f评价课程 [{course_name}] 时发生未知错误: {e}) self.driver.save_screenshot(ferror_{course_name.replace(“/”, “_”)}.png) return False finally: # 无论成功与否短暂等待后返回课程列表页准备下一门 time.sleep(random.uniform(1, 2)) # 这里可能需要点击“返回列表”按钮或者直接再次访问列表URL self.driver.get(http://your-jwxt.com/student/teachingEvaluation/toEvaluateList) time.sleep(1)核心技巧表单元素定位策略对于单选/复选框按name属性分组是最高效的方式。find_elements(By.NAME, “question1”)会返回该问题所有选项的WebElement列表。评价策略脚本中提供了随机选择和固定高分两种策略。从伦理和实际效果考虑建议固定选择中性或积极的选项如全部“良好”避免随机产生极端差评。这既是尊重教师也符合评教的本意完成必要流程。滚动操作execute_script(“arguments[0].scrollIntoView();”, element)确保元素在视窗内这对于那些需要滚动才能看到或点击的元素至关重要。提交后等待提交后必须有足够的等待时间让服务器处理并跳转。除了固定等待time.sleep最好结合显式等待检查成功提示或URL变化。4.4 主流程控制与优雅退出将以上模块串联起来并加入日志和异常处理。def run(self, login_url): 主运行流程 if not self.login(login_url): print(登录失败程序终止。) self.driver.quit() return if not self.navigate_to_evaluation(): print(导航至评教页面失败程序终止。) self.driver.quit() return course_list self.get_course_links() if not course_list: print(未找到待评课程可能已全部评价完成。) self.driver.quit() return success_count 0 fail_count 0 for idx, course in enumerate(course_list, 1): print(f\n--- 进度 [{idx}/{len(course_list)}] ---) if self.evaluate_single_course(course): success_count 1 else: fail_count 1 # 可选如果单门课失败是继续还是终止 # 这里选择记录并继续下一门 print(f课程评价失败已记录。继续下一门。) # 每评价完几门课可以稍作长时间休息模拟人工 if idx % 5 0: wait_time random.uniform(5, 10) print(f已评价{idx}门课程休息{wait_time:.1f}秒...) time.sleep(wait_time) print(f\n{*30}) print(f评教任务完成) print(f成功: {success_count} 门 失败: {fail_count} 门) print(f{*30}) # 所有任务完成后保持浏览器打开一段时间供查看然后退出 input(所有任务执行完毕按回车键关闭浏览器...) self.driver.quit() # 使用示例 if __name__ __main__: # 重要请在此处填写你的真实教务系统登录地址、学号和密码 JWXT_LOGIN_URL http://jwxt.your-university.edu.cn/login # 替换为实际登录页URL STUDENT_ID 你的学号 PASSWORD 你的密码 evaluator AutoEvaluation(STUDENT_ID, PASSWORD) evaluator.run(JWXT_LOGIN_URL)5. 常见问题排查与实战经验即使代码逻辑正确在实际运行中也会遇到各种意想不到的问题。以下是我在多次实践中总结的“避坑指南”。5.1 元素定位失败NoSuchElementException, TimeoutException这是最高频的错误。可能原因1页面未加载完成。解决增加显式等待等待目标元素或其父容器出现。不要用find_element后立即操作。技巧有时元素已存在但不可交互如被遮挡、未启用应使用EC.element_to_be_clickable。可能原因2元素在iframe或shadow DOM内。解决使用driver.switch_to.frame(frame_reference)切换到对应的iframe后再定位元素。操作完后用driver.switch_to.default_content()切回。可能原因3元素是动态生成的选择器不对。解决使用浏览器开发者工具的Console用$$(“你的CSS选择器”)或$x(“你的XPath”)测试选择器是否能准确找到元素。优先使用稳定的属性如>try: alert driver.switch_to.alert print(f检测到弹窗内容: {alert.text}) alert.accept() # 点击确定 # 或者 alert.dismiss() 点击取消 except: pass # 没有弹窗继续5.5 性能与稳定性优化资源清理确保在脚本结束无论正常或异常时调用driver.quit()关闭浏览器进程避免内存泄漏。异常恢复对于单门课评价失败脚本应能记录错误并继续下一门而不是整体崩溃。使用try...except包裹核心操作并保存错误截图和日志。日志记录使用Python的logging模块替代print可以输出到文件方便事后排查。配置分离将URL、账号密码、选择器等配置信息放在单独的config.py文件或环境变量中不要硬编码在脚本里。6. 安全、伦理与扩展思考在享受自动化便利的同时我们必须清醒地认识到其边界。合规使用本脚本仅用于个人学习Python自动化技术及完成个人合法的评教任务。严禁用于任何形式的恶意刷评、攻击服务器、干扰系统正常运行或侵犯他人权益的行为。使用前请务必阅读学校的相关规章制度。账号安全脚本中直接明文存储密码存在风险。对于学习项目可以这样但在任何情况下都不应将此类脚本上传至公开的代码仓库如GitHub。可以考虑从命令行参数、加密文件或环境变量中读取敏感信息。扩展方向GUI界面使用PyQt5或Tkinter为脚本制作一个简单的图形界面方便输入账号和选择评教策略。配置化将不同学校的教务系统页面结构选择器、URL抽象成配置文件使脚本能够适配多个系统。定时任务结合Windows任务计划程序或Linux的cron在评教开放期间定时启动脚本。状态持久化将已评价的课程记录到本地文件或数据库避免脚本意外中断后重复评价。邮件通知脚本运行完成后通过smtplib发送邮件报告成功/失败情况。这个“教务系统自动评教”项目从技术上看是Web自动化入门到精通的绝佳案例从实用角度看它能切实节省你的时间。最重要的是在实现它的过程中你会系统地学习到元素定位、等待机制、异常处理、模拟交互等一系列核心技能这些技能在未来的爬虫、自动化测试、RPA机器人流程自动化等场景中都将大放异彩。动手去实现它然后享受自动化带来的那份效率与优雅吧。
返回列表