1. 项目概述与核心价值
最近几年,线上学习平台,特别是像雨课堂这类与高校课程深度绑定的MOOC平台,已经成为我们获取知识、完成课业任务的主要场景之一。作为一名经常需要处理大量重复性学习任务的学生,或者是一位需要管理多个课程进度的助教,你是否也经历过这样的场景:为了完成平台要求的视频观看时长、章节测验或者讨论区发帖,不得不守在电脑前,手动点击播放、等待、答题、提交,整个过程枯燥且耗时。这种重复性劳动不仅效率低下,还容易因为人为疏忽导致任务遗漏或超时。正是在这种背景下,利用Python和Selenium开发一个自动化学习工具的想法应运而生。这个工具的核心目标,就是模拟人类在浏览器中的操作,自动完成登录、课程导航、视频播放、测验答题等一系列流程,将我们从繁琐的机械操作中解放出来,把宝贵的时间投入到真正需要思考和创造的学习环节中去。
这里需要明确一个核心前提:我们讨论的自动化工具,其设计初衷是 辅助学习、提升效率 ,而非用于学术不端。它适用于处理那些形式化、重复性的平台交互任务(如确保视频进度、完成客观题测验),从而让你能更专注于课程的核心内容理解与主观思考。工具本身是中性的,关键在于使用者的目的和方式。接下来,我将以一个资深开发者的视角,为你拆解如何从零开始构建这样一个工具,涵盖环境搭建、核心逻辑设计、关键代码实现以及实际开发中必然会遇到的“坑”与解决方案。整个指南将力求详尽,确保即使你是Python和Selenium的初学者,也能跟随步骤一步步实现。
2. 环境准备与工具选型解析
工欲善其事,必先利其器。在开始编码之前,搭建一个稳定、高效的开发环境是第一步。我们的技术栈非常明确:Python作为主编程语言,Selenium用于Web自动化。但仅仅知道这些还不够,我们需要做出具体的选择。
2.1 Python环境与IDE选择
首先,你需要一个Python解释器。我强烈推荐从Python官网下载最新稳定版本(如3.8+)。对于新手,在安装时务必勾选“Add Python to PATH”选项,这能避免后续在命令行中调用Python时出现“命令未找到”的错误。安装完成后,打开终端(Windows是CMD或PowerShell,Mac/Linux是Terminal),输入 python --version 来验证安装是否成功。
接下来是集成开发环境(IDE)。对于自动化脚本开发,Visual Studio Code(VSCode)和PyCharm都是极佳的选择。VSCode轻量、插件丰富,通过安装Python扩展和Pylance等插件,可以获得优秀的代码提示、调试和格式化体验。PyCharm作为专业的Python IDE,开箱即用,对项目管理和虚拟环境支持更友好。我个人更倾向于VSCode,因为它对前端调试和脚本快速编辑的支持更灵活,而且资源占用相对较少。无论选择哪个,请确保配置好Python解释器路径。
一个至关重要的习惯是使用虚拟环境。虚拟环境能为每个项目创建独立的Python包安装空间,避免不同项目间的依赖冲突。在项目根目录下,使用命令 python -m venv venv 创建一个名为 venv 的虚拟环境,然后激活它(Windows: venv\Scripts\activate , Mac/Linux: source venv/bin/activate )。激活后,你的命令行提示符前会出现 (venv) 字样。
2.2 Selenium与浏览器驱动
Selenium是一个强大的浏览器自动化框架,它通过WebDriver协议与真实浏览器进行通信。我们的工具将依靠它来“操纵”浏览器。
- 安装Selenium库 :在激活的虚拟环境中,运行
pip install selenium。这是最基础的一步。 - 选择浏览器与下载驱动 :Selenium支持Chrome、Firefox、Edge等主流浏览器。考虑到兼容性和性能,Chrome/Chromium系浏览器是首选。你需要下载与你的 浏览器版本严格匹配 的ChromeDriver。打开Chrome,在地址栏输入
chrome://version/查看“Google Chrome”后面的版本号(例如,120.0.6099.110)。然后去ChromeDriver官网或国内镜像站,下载对应版本号的驱动。将下载的chromedriver.exe(Windows)或chromedriver(Mac/Linux)文件放在一个你记得住的目录,或者更规范的做法是,将其所在目录添加到系统的PATH环境变量中。
注意 :浏览器自动更新很常见,一旦浏览器升级而驱动未更新,Selenium就会报错。因此,要么关闭浏览器自动更新,要么在脚本中集成驱动版本检查与自动下载的逻辑(可使用
webdriver-manager这类第三方库简化此过程)。
2.3 辅助工具库
除了Selenium,我们可能还需要一些辅助库来让工具更强大、更易用:
-
webdriver-manager:如前所述,它可以自动管理浏览器驱动的下载和匹配,省去手动维护的麻烦。安装:pip install webdriver-manager。 -
openpyxl或pandas:如果你的工具需要从Excel表格中读取课程列表、账号密码或题目答案,这些库会非常有用。 -
schedule:如果你希望工具能在特定时间自动运行(例如,每天凌晨自动刷课),这个轻量级的定时任务库是个不错的选择。 -
pyautogui:在极少数情况下,如果遇到Selenium无法处理的非Web元素(如浏览器弹出的原生文件下载对话框),可能需要用它来模拟键盘鼠标操作。但这应作为最后的手段。
3. 核心自动化逻辑设计与实现
环境就绪后,我们来构思工具的核心工作流。一个完整的自动化学习流程通常包括:启动并配置浏览器 -> 登录雨课堂 -> 遍历课程 -> 进入具体章节 -> 处理视频 -> 处理测验 -> 退出。我们将分步拆解。
3.1 浏览器启动与反检测策略
直接使用 webdriver.Chrome() 启动的浏览器,会被一些网站(包括部分学习平台)识别出是自动化程序,可能导致登录失败或功能受限。因此,我们需要对浏览器进行“伪装”。
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
import time
def create_stealth_driver():
chrome_options = Options()
# 1. 添加常见的用户代理(User-Agent),模拟真实浏览器
chrome_options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
# 2. 禁用自动化控制标志,这是最关键的一步
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
# 3. 修改 navigator.webdriver 属性为 undefined
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
# 4. 其他优化选项
chrome_options.add_argument('--disable-gpu') # 禁用GPU加速,有时可增加稳定性
chrome_options.add_argument('--no-sandbox') # 在Linux或某些Docker环境下可能需要
chrome_options.add_argument('--disable-dev-shm-usage') # 解决共享内存问题
# 5. 可以设置为无头模式(不显示浏览器界面),适合在服务器后台运行
# chrome_options.add_argument('--headless=new') # Chrome 109+ 推荐用法
# 6. 使用webdriver-manager自动管理驱动
service = webdriver.ChromeService(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
# 7. 执行CDP命令,进一步覆盖可能暴露的自动化特征
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
'''
})
return driver
# 使用函数创建驱动实例
driver = create_stealth_driver()
driver.get("https://www.yuketang.cn/") # 访问雨课堂首页
time.sleep(3) # 等待页面加载,实际开发中应使用更智能的等待
实操心得 :反检测是一个持续对抗的过程。上述配置能应对大多数基础检测。如果仍然被识别,可能需要更复杂的策略,如随机化操作间隔、模拟人类鼠标移动轨迹(可使用 ActionChains 轻微移动)、或者使用更底层的浏览器自动化工具如Playwright(它提供了更好的上下文隔离)。但切记,我们的目的是辅助学习,不应过度追求破解平台的所有防护。
3.2 登录模块实现
登录是第一个关键环节。雨课堂通常支持账号密码登录和扫码登录。自动化脚本更适合处理账号密码形式。
def login_yuketang(driver, username, password):
try:
driver.get("https://www.yuketang.cn/web")
# 显式等待登录按钮出现,比固定sleep更可靠
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
# 点击“登录”按钮,进入登录框
login_entry = wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "login-btn")))
login_entry.click()
time.sleep(1) # 等待登录模态框弹出
# 切换到账号密码登录标签(如果默认不是)
# 需要根据实际页面结构查找元素,这里为示例
# tab_pwd = driver.find_element(By.XPATH, '//div[text()="账号密码登录"]')
# tab_pwd.click()
# time.sleep(0.5)
# 定位账号和密码输入框并输入信息
# 元素的定位器(如ID, CLASS_NAME, XPATH)需要通过浏览器开发者工具手动查看
# 示例定位,实际值需自行查看页面源码
username_input = wait.until(EC.presence_of_element_located((By.ID, "phone")))
password_input = driver.find_element(By.ID, "password")
username_input.clear()
username_input.send_keys(username)
time.sleep(0.5) # 模拟人类输入间隔
password_input.clear()
password_input.send_keys(password)
time.sleep(0.5)
# 点击登录按钮
submit_btn = driver.find_element(By.CSS_SELECTOR, "button.login-button")
submit_btn.click()
# 等待登录成功,通常可以通过检查用户头像或特定元素出现来判断
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CLASS_NAME, "user-avatar"))
)
print("登录成功!")
return True
except Exception as e:
print(f"登录过程中出现错误: {e}")
# 可以在这里截图保存,便于调试
driver.save_screenshot("login_error.png")
return False
注意事项 :
- 元素定位 :这是Selenium自动化中最核心也最易变的部分。网站的UI可能随时改版,导致你写好的定位器失效。因此,代码中不要使用过于脆弱定位器(如绝对XPATH)。优先使用ID、稳定的CLASS或相对XPATH。定期检查脚本的健壮性。
- 等待策略 :绝对避免到处使用
time.sleep(固定秒数)。这既低效又不稳定。务必使用Selenium提供的 显式等待 (WebDriverWait),它会在条件满足(如元素可见、可点击)后立即继续,否则超时抛出异常。对于页面整体加载,可以使用driver.implicitly_wait(10)设置隐式等待作为兜底,但显式等待更精确。 - 验证码 :如果平台弹出图形验证码或滑块验证,自动化处理将变得非常复杂。可以考虑:① 在脚本中集成打码平台API(涉及额外费用和稳定性);② 设计流程在出现验证码时暂停,提醒用户手动处理后再继续;③ 尝试分析平台策略,在特定时间段或操作频率下可能不会触发验证码。
3.3 课程与章节遍历逻辑
登录成功后,需要找到目标课程并进入。雨课堂的“我的课程”页面通常是一个课程列表。
def navigate_to_course(driver, course_name_keyword):
"""
根据课程名称关键词导航到指定课程页面
"""
try:
# 假设首页或主导航有“我的课程”入口
my_course_link = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.LINK_TEXT, "我的课程"))
)
my_course_link.click()
# 等待课程列表加载
WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.CLASS_NAME, "course-card"))
)
# 查找包含关键词的课程卡片
course_cards = driver.find_elements(By.CLASS_NAME, "course-card")
target_card = None
for card in course_cards:
if course_name_keyword in card.text:
target_card = card
break
if target_card:
target_card.click()
print(f"已进入课程: {course_name_keyword}")
# 等待课程详情页加载完成
time.sleep(3)
return True
else:
print(f"未找到包含关键词 '{course_name_keyword}' 的课程")
return False
except Exception as e:
print(f"导航至课程失败: {e}")
return False
def process_all_chapters(driver):
"""
处理当前课程的所有章节
"""
# 1. 获取章节列表
# 章节可能位于侧边栏或一个可折叠的列表中
chapter_elements = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".chapter-item"))
)
print(f"发现 {len(chapter_elements)} 个章节")
for index, chapter_elem in enumerate(chapter_elements):
# 为了避免StaleElementReferenceException(元素过期),每次重新获取列表或使用更稳定的定位方式
# 这里我们选择在每次循环时重新查找当前索引的章节
chapter_list = driver.find_elements(By.CSS_SELECTOR, ".chapter-item")
if index >= len(chapter_list):
break
current_chapter = chapter_list[index]
chapter_title = current_chapter.text.split('\n')[0] # 简单提取标题
print(f"正在处理第 {index+1} 章: {chapter_title}")
# 点击进入该章节
current_chapter.click()
time.sleep(2) # 等待章节内容加载
# 2. 处理本章节内的所有学习项目(视频、测验、文档等)
process_chapter_content(driver)
# 3. 处理完后,可能需要返回章节列表页面
# 有些页面是SPA(单页应用),点击章节后内容区域更新,无需返回
# 如果是跳转到新页面,则需要 driver.back()
# driver.back()
# time.sleep(1)
核心思路 :遍历的逻辑关键在于稳定地定位到可交互的章节元素列表,并处理好页面状态变化。单页应用(SPA)和传统多页应用的处理方式不同。SPA中,点击章节可能只是通过Ajax更新内容区域,DOM元素不会完全刷新,但章节列表本身可能是一个动态组件。多页应用则可能需要使用 driver.back() 返回列表页。你需要通过观察雨课堂的实际交互行为来确定模式。
3.4 视频播放自动化
视频播放是MOOC学习的核心。自动化视频播放的目标通常是确保视频进度达到100%。
def process_video(driver):
"""
处理当前页面中的视频元素
"""
try:
# 查找视频播放器容器或iframe
# 雨课堂的视频可能嵌入在iframe中
video_frames = driver.find_elements(By.TAG_NAME, "iframe")
video_container = None
if video_frames:
# 切换到视频iframe内部
driver.switch_to.frame(video_frames[0])
print("已切换到视频iframe")
# 在iframe内查找视频元素
video_element = driver.find_element(By.TAG_NAME, "video")
else:
# 如果视频直接嵌入在页面中
video_element = driver.find_element(By.TAG_NAME, "video")
# 获取视频总时长和当前播放时间
total_duration = driver.execute_script("return arguments[0].duration;", video_element)
print(f"视频总时长: {total_duration} 秒")
# 如果视频未播放,则点击播放
is_paused = driver.execute_script("return arguments[0].paused;", video_element)
if is_paused:
print("视频暂停中,开始播放...")
driver.execute_script("arguments[0].play();", video_element)
time.sleep(2) # 等待播放开始
# 监控播放进度
poll_interval = 10 # 每10秒检查一次进度
last_progress = 0
while True:
current_time = driver.execute_script("return arguments[0].currentTime;", video_element)
progress = (current_time / total_duration) * 100 if total_duration > 0 else 0
# 防止平台检测:随机化检查间隔,并模拟一些非规律性的鼠标移动(可选)
# time.sleep(random.uniform(poll_interval-2, poll_interval+2))
print(f"当前播放进度: {progress:.2f}%")
# 如果进度长时间未变化,可能是卡住了或需要互动(如弹题)
if abs(progress - last_progress) < 0.1 and progress < 99:
print("进度可能卡住,尝试点击视频区域激活...")
video_element.click()
time.sleep(2)
last_progress = progress
# 判断是否播放完毕(接近100%或currentTime接近duration)
if progress >= 99.5 or (total_duration - current_time) < 2:
print("视频播放完毕或即将结束。")
break
# 检查页面是否有中途弹出的测验(弹题)
check_and_handle_popup_quiz(driver)
time.sleep(poll_interval)
# 播放结束后,如果之前在iframe中,需要切换回主文档
if video_frames:
driver.switch_to.default_content()
print("已切换回主文档")
return True
except Exception as e:
print(f"处理视频时发生错误: {e}")
# 出错时也尝试切换回主文档
try:
driver.switch_to.default_content()
except:
pass
return False
def check_and_handle_popup_quiz(driver):
"""
检查并处理视频播放过程中弹出的测验题
"""
try:
# 寻找弹题模态框的特定元素,例如一个包含题目文本的div
# 这需要你实际观察弹题出现时的页面结构
quiz_modal = driver.find_elements(By.CSS_SELECTOR, ".popup-quiz-modal")
if quiz_modal and quiz_modal[0].is_displayed():
print("检测到视频弹题,正在处理...")
# 这里简化处理:随机选择一个答案选项并提交
# 实际应用中,你可能需要更复杂的逻辑,比如从题库匹配答案
options = quiz_modal[0].find_elements(By.CSS_SELECTOR, ".option-item")
if options:
import random
random.choice(options).click()
time.sleep(1)
# 查找并点击提交按钮
submit_btn = quiz_modal[0].find_element(By.CSS_SELECTOR, ".submit-btn")
submit_btn.click()
print("已提交弹题答案(随机选择)。")
time.sleep(2) # 等待弹题关闭
except Exception as e:
# 没找到弹题是正常情况,静默处理
pass
避坑技巧 :
- iframe处理 :很多在线教育平台将视频播放器放在
<iframe>内以隔离环境。你必须使用driver.switch_to.frame()切换到iframe内部才能操作视频元素,操作完毕后务必用driver.switch_to.default_content()切回来,否则后续操作会找不到元素。 - JavaScript执行 :直接通过WebElement的
.click()或.send_keys()方法有时对视频控件无效。这时,通过driver.execute_script()直接执行JavaScript代码来调用视频元素的play()、pause()方法或修改其currentTime属性,通常更可靠。 - 进度监控与防检测 :简单的
while循环加sleep监控进度容易被识别。可以加入随机延迟、模拟鼠标在视频区域轻微晃动(使用ActionChains)等行为。更重要的是,不要试图通过直接设置currentTime = duration来跳转进度,很多平台会记录异常播放行为。 - 弹题处理 :视频中途弹出的测验是常见反自动化手段。上述代码提供了一个基础的检测和随机应答框架。更高级的做法需要OCR识别题目文字,或者预先建立题库进行匹配,但这会大大增加复杂度。
3.5 章节测验自动化答题
章节测验通常包含单选题、多选题、判断题和填空题。
def handle_quiz(driver):
"""
处理当前页面的测验(非视频弹题)
"""
try:
# 等待测验区域加载
quiz_section = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "quiz-container"))
)
print("发现测验区域")
# 获取所有题目
questions = quiz_section.find_elements(By.CLASS_NAME, "question-item")
print(f"共发现 {len(questions)} 道题目")
for q_index, question in enumerate(questions):
print(f"正在处理第 {q_index+1} 题")
# 提取题目文本(用于可能的题库匹配)
question_text_elem = question.find_element(By.CSS_SELECTOR, ".question-stem")
q_text = question_text_elem.text.strip()[:100] # 取前100字符作为标识
print(f"题目摘要: {q_text}...")
# 判断题型
# 1. 单选题 (radio buttons)
radio_options = question.find_elements(By.CSS_SELECTOR, "input[type='radio']")
# 2. 多选题 (checkboxes)
checkbox_options = question.find_elements(By.CSS_SELECTOR, "input[type='checkbox']")
# 3. 填空题 (input text)
text_inputs = question.find_elements(By.CSS_SELECTOR, "input[type='text'], textarea")
if radio_options:
handle_single_choice(question, radio_options)
elif checkbox_options:
handle_multi_choice(question, checkbox_options)
elif text_inputs:
handle_fill_in_blank(question, text_inputs)
else:
print("未知题型,尝试查找可点击的选项标签")
# 备选方案:查找所有选项标签并点击第一个
option_labels = question.find_elements(By.CSS_SELECTOR, ".option-label")
if option_labels:
option_labels[0].click()
print("已点击第一个选项标签作为默认处理。")
# 每处理完一题,稍作停顿,模拟人类速度
time.sleep(random.uniform(1.0, 2.5))
# 所有题目处理完毕后,查找并点击提交按钮
submit_button = quiz_section.find_element(By.CSS_SELECTOR, "button.submit-btn, input[type='submit']")
submit_button.click()
print("已提交测验答案。")
time.sleep(3) # 等待提交结果页面
# 检查提交结果,例如是否有错题解析
try:
result_info = driver.find_element(By.CLASS_NAME, "quiz-result").text
print(f"测验结果: {result_info}")
except:
print("未找到明确的测验结果信息。")
return True
except Exception as e:
print(f"处理测验时发生错误: {e}")
driver.save_screenshot("quiz_error.png")
return False
def handle_single_choice(question_element, options):
"""处理单选题"""
# 策略1: 随机选择
# import random
# random.choice(options).click()
# 策略2: 基于简单关键词匹配(非常基础的示例)
q_text = question_element.find_element(By.CSS_SELECTOR, ".question-stem").text.lower()
if "python" in q_text and "创始人" in q_text:
# 假设我们知道答案是“Guido van Rossum”
for opt in options:
# 找到与选项关联的label文本
opt_id = opt.get_attribute("id")
if opt_id:
label = question_element.find_element(By.CSS_SELECTOR, f"label[for='{opt_id}']")
if "guido" in label.text.lower():
opt.click()
print("根据关键词选择了答案。")
return
# 默认随机选择
if options:
options[0].click()
print("默认选择第一个选项。")
def handle_multi_choice(question_element, options):
"""处理多选题"""
# 多选题策略更复杂,通常需要知道正确答案组合
# 这里作为示例,我们随机选择1到N个选项
import random
options_to_select = random.sample(options, k=random.randint(1, len(options)))
for opt in options_to_select:
if not opt.is_selected():
opt.click()
print(f"随机选择了 {len(options_to_select)} 个选项。")
def handle_fill_in_blank(question_element, inputs):
"""处理填空题"""
for input_box in inputs:
# 根据输入框的placeholder或前置文本猜测应填内容
placeholder = input_box.get_attribute("placeholder") or ""
# 一个非常简陋的匹配逻辑示例
if "姓名" in placeholder:
input_box.send_keys("张三")
elif "学号" in placeholder:
input_box.send_keys("20230001")
else:
# 默认填写一些通用文本
input_box.send_keys("已学习")
print(f"已填写输入框: {placeholder}")
核心挑战与策略 :
- 答案来源 :这是自动化答题最大的难点。合法且可持续的策略包括:
- 预置题库 :如果你有课程的习题集或往年答案,可以构建一个本地数据库(如SQLite、JSON文件),通过题目文本模糊匹配来查找答案。
- OCR识别 :对于图片形式的题目,可以集成Tesseract等OCR库进行识别,再匹配答案。
- 协作学习 :在符合平台规则和道德的前提下,设计工具从允许的学习社区或讨论区安全地获取提示(这需要极其谨慎,避免违规爬虫)。
- 保守策略 :对于无法确定答案的题目,选择跳过(如果允许)、填写中性答案或随机选择,并记录下题目,后续人工处理。 绝对不要 尝试暴力破解或攻击平台服务器获取答案。
- 动态内容与反爬 :测验页面可能采用动态加载,题目和选项在点击后才渲染。需要分析网络请求(XHR/Fetch),或者使用Selenium的等待机制确保元素完全交互。复杂的页面可能使用Canvas渲染题目,这时Selenium无法直接获取文本,需要借助OCR。
4. 工程化提升与稳定性保障
一个能长期稳定运行的脚本,不能只是简单的线性流程。我们需要考虑错误处理、日志记录、配置管理等问题。
4.1 配置管理与数据持久化
将账号、课程列表、策略参数等外部信息与代码分离。
# config.yaml (或 config.json)
# yuketang:
# username: "your_student_id"
# password: "your_password"
# courses:
# - "大学计算机基础"
# - "学术英语写作"
# strategy:
# video_wait_time: 10
# random_delay_range: [0.5, 2.0]
# headless: false
import yaml
import json
import os
CONFIG_FILE = "config.yaml"
def load_config():
if not os.path.exists(CONFIG_FILE):
# 创建默认配置模板
default_config = {
"yuketang": {
"username": "",
"password": "",
"courses": [],
"strategy": {
"video_wait_time": 10,
"random_delay_range": [0.5, 2.0],
"headless": False
}
}
}
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
yaml.dump(default_config, f, allow_unicode=True)
print(f"已创建配置文件模板,请编辑 {CONFIG_FILE}")
exit(1)
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
return config
# 在脚本主函数中加载配置
config = load_config()
USERNAME = config['yuketang']['username']
PASSWORD = config['yuketang']['password']
TARGET_COURSES = config['yuketang']['courses']
4.2 健壮的错误处理与日志记录
脚本可能在网络波动、页面改版、元素找不到等各种情况下出错。良好的错误处理能保证脚本在部分失败后仍能继续,或者至少留下清晰的故障信息。
import logging
from datetime import datetime
# 设置日志
log_filename = f"yuketang_auto_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_filename, encoding='utf-8'),
logging.StreamHandler() # 同时输出到控制台
]
)
logger = logging.getLogger(__name__)
def safe_click(element, description=""):
"""一个安全的点击函数,包含重试机制"""
retries = 3
for i in range(retries):
try:
element.click()
logger.info(f"成功点击: {description}")
return True
except Exception as e:
logger.warning(f"点击失败 ({i+1}/{retries}): {description}, 错误: {e}")
time.sleep(2)
# 可以尝试滚动元素到视图
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)
logger.error(f"点击最终失败: {description}")
return False
# 在主循环中使用try-except捕获全局异常
def main():
driver = None
try:
driver = create_stealth_driver()
if not login_yuketang(driver, USERNAME, PASSWORD):
logger.error("登录失败,程序终止。")
return
for course in TARGET_COURSES:
logger.info(f"开始处理课程: {course}")
try:
if navigate_to_course(driver, course):
process_all_chapters(driver)
else:
logger.warning(f"未找到或无法进入课程: {course}")
except Exception as e:
logger.error(f"处理课程 {course} 时发生严重错误: {e}", exc_info=True)
# 截图保存现场
driver.save_screenshot(f"error_course_{course}_{int(time.time())}.png")
# 可以选择跳过此课程,继续下一个
continue
logger.info("所有课程处理完成!")
except KeyboardInterrupt:
logger.info("用户中断程序。")
except Exception as e:
logger.critical(f"程序运行中出现未捕获的异常: {e}", exc_info=True)
finally:
if driver:
logger.info("正在关闭浏览器...")
driver.quit()
4.3 部署与定时运行
开发完成后,你可能希望脚本能自动定时运行。
- 本地定时任务(Windows任务计划程序 / macOS launchd / Linux cron) :这是最直接的方式。将你的Python脚本打包成一个可执行的命令,然后在系统自带的定时任务工具中设置执行周期(例如,每天凌晨2点)。确保任务运行时,Python环境和所有依赖都已就绪。
- 使用 schedule 库内部循环 :在脚本内部使用
schedule库定义任务周期,然后让脚本长时间运行。这种方式更适合在云服务器或常年开机的电脑上运行。import schedule import time def job(): logger.info("开始执行自动化学习任务...") main() # 调用你的主函数 schedule.every().day.at("02:00").do(job) # 每天2点执行 while True: schedule.run_pending() time.sleep(60) # 每分钟检查一次 - 打包成可执行文件 :使用
PyInstaller或cx_Freeze将脚本和依赖打包成单个.exe(Windows)或可执行文件,方便在没有Python环境的电脑上运行。命令示例:pyinstaller --onefile --clean your_script.py。注意,打包时可能需要处理浏览器驱动的路径问题,通常需要将驱动文件放在与可执行文件相同的目录,并在代码中指定相对路径。
5. 伦理边界、风险与注意事项
在结束这篇指南之前,我必须强调自动化工具使用的伦理和法律边界。这是每个开发者和使用者都必须严肃对待的问题。
- 尊重平台规则 :仔细阅读雨课堂或任何其他学习平台的服务条款。明确禁止自动化访问的条款,使用工具可能违反协议,导致账号被封禁。
- 辅助而非替代 :工具的定位应是“辅助工具”,帮助处理那些重复、机械的交互流程,从而节省出时间用于深度思考和学习。绝不能用于代替全部学习过程,如自动观看所有视频并完成所有测验,而本人完全不参与。这违背了教育的初衷,也属于学术不端。
- 控制频率与行为 :在脚本中设置合理的延迟,模拟人类操作速度,避免高频请求对服务器造成压力。不要尝试并发操作多个账号或课程。
- 数据隐私 :妥善保管你的配置文件,特别是账号密码。不要将包含敏感信息的代码上传到公开的GitHub仓库。
- 技术风险 :网站前端结构变化是常态。你的脚本可能需要定期维护和更新。过度复杂的反检测策略可能触发平台更严格的风控。
- 学术诚信 :最终,你需要对你提交的作业和获得的成绩负责。自动化工具完成的只能是流程性的部分,核心的知识理解和创造性产出必须由你本人完成。
开发这样一个工具本身是一个极佳的编程实践项目,它能让你深入理解Web自动化、网络协议、反爬虫策略和软件工程。但在实际应用时,请务必保持清醒,在技术便利与学术诚信之间找到平衡点。希望这篇详尽的指南不仅能帮你构建工具,更能让你理解其背后的原理与界限。如果在开发中遇到具体的技术问题,多查阅Selenium官方文档、浏览器开发者工具以及相关的技术社区,那将是解决问题的最佳途径。
1万+

被折叠的 条评论
为什么被折叠?



