我教的《计算物理》这门课期末考试除了笔试之外还有一个期末论文,让学生在超星学习通里提交。由于要求学生把期末论文,程序代码等压缩成压缩包提交,所以需要下载下来才能看。
教学平台并没有提供一键批量下载所有附件的功能。面对满屏的学生名单,我陷入了沉思:
难道我真的要对着这百号人,机械地执行“点开-进入-下载-关闭”的操作吗?
虽然一个一个点,可能 1 小时也能点完;但写一个自动化脚本可能需要 3 小时。可我的逻辑是:写代码的时间是“创造”,手动点的时间是“消耗”。以后再遇到这种情况,我只需要按下回车,然后去喝杯咖啡。
1. 自动化的“三步走”逻辑
想要让浏览器自己动起来,我们需要模拟人类的操作路径:
左侧切人:在学生列表里选中一个名字。
中间进详情:点击“编辑记录”。
新窗口下载:在弹出的详情页里,穿透iframe框架,点击那个.zip附件。
2. 那些差点让我放弃的“坑”
在编写这个脚本时,我经历了三次“为什么代码找不到元素”的灵魂拷问:
窗口之谜:点击“编辑记录”后会弹出新窗口,Selenium 的“眼睛”却还留在旧窗口,必须手动执行switch_to.window才能把焦点换过去。
消失的选择器:超星系统的列表结构很深,有时候代码运行太快,列表还没加载完就去点,直接报错。最后靠WebDriverWait(显式等待)才稳住。
套娃式的 iframe:这是最隐蔽的!详情页里的附件竟然藏在内嵌框架(iframe)里,就像在一个盒子里又套了一个盒子。如果不先执行switch_to.frame,代码永远摸不到下载按钮。
3. 最终的“懒人”脚本
如果你也面临类似的重复劳动,可以直接复用这段代码。只需填入你的驱动路径,登录后按回车,它就开始帮你干活了。
# -*- coding: utf-8 -*-"""@author: chenweiguang"""from selenium import webdriverfrom selenium.webdriver.chrome.service import Service # 新增:导入 Service 类from selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECimport time# 1. 在这里填入你 ChromeDriver 的实际存放路径# 注意:在 Windows 中路径建议使用双反斜杠 \\ 或者在字符串前加 rdriver_path = r"D:\Soft\chromedriver-win64\chromedriver.exe" # 2. 初始化 Serviceservice = Service(executable_path=driver_path)# 3. 启动浏览器时传入 serviceoptions = webdriver.ChromeOptions()driver = webdriver.Chrome(service=service, options=options)# --- 后面的逻辑保持不变 ---driver.get("https://i.mooc.chaoxing.com/space/index?t=1769235712794")input("请在网页中登录并进入学生列表页面后,按回车开始...")# 假设你已经定义了 driver 并且当前处于学生列表主页面main_window = driver.current_window_handledef start_automation(): # 使用你提供的源代码中的选择器 student_selector = "ul.group-wrapper-list li.group-list" # 尝试获取学生列表 students_elements = driver.find_elements(By.CSS_SELECTOR, student_selector) total_students = len(students_elements) // 2 print(f"检测到共 {total_students} 名学生。") for i in range(total_students): try: # 重新获取元素防止失效 current_list = driver.find_elements(By.CSS_SELECTOR, student_selector) student = current_list[i] # 滚动并点击 driver.execute_script("arguments[0].scrollIntoView();", student) time.sleep(0.5) student.click() print(f"正在处理第 {i+1}/{total_students} 位学生...") # 等待“编辑记录”并点击 edit_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, "span.edit")) ) edit_btn.click() # 切换窗口 WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) > 1) original_window = driver.current_window_handle for window in driver.window_handles: if window != original_window: driver.switch_to.window(window) break # 【关键步骤】在新窗口中切换 iframe # 先给一点时间让新窗口的框架加载 time.sleep(1) driver.switch_to.default_content() # 先到主文档 iframes = driver.find_elements(By.TAG_NAME, "iframe") if len(iframes) > 0: driver.switch_to.frame(iframes[0]) print(" - 已进入新窗口 iframe") # 4. 定位并点击下载按钮 (div.attach_item) # 使用 JS 点击最为稳妥 attachment_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, "div.attach_item")) ) attachment_btn.click() print(" - 下载指令发送成功") time.sleep(2) # 留出下载开始的缓冲时间 driver.close() driver.switch_to.window(original_window) except Exception as e: print(f"处理第 {i+1} 位时出错: {e}") if len(driver.window_handles) > 1: driver.close() driver.switch_to.window(original_window) print("--- 全部完成 ---")if __name__ == "__main__": start_automation()
4. 写在最后
虽然这次写脚本加调试花了我不少时间,甚至比我手动点一遍还要久。但这种“一次编写,到处运行”的成就感是无可比拟的。
更重要的是把这类问题的底层逻辑(窗口切换、框架穿透、异步加载)摸透了。下次再遇到类似的“套路”,只需要改一行选择器(Selector),就能再次实现“摸鱼自由”。
愿你的每一个重复劳动,都能被一行代码终结。
注:代码是在AI的辅助下编写的,文章内容也是让AI辅助写的,然后最后修改了一下,封面图也是用AI生成的。有了AI确实可以少花很多功夫。