20242312 2024-2025-2 《Python程序设计》实验四报告

20242312 2024-2025-2 《Python程序设计》实验四报告

课程:《Python程序设计》
班级: 2423
姓名: 周梓濠
学号:20242312
实验教师:王志强
实验日期:2025年5月30日
必修/选修: 公选课

一、选题背景及需求分析

(1)选题背景

对于王老师每节课前让人抓狂的手势签到,我每次都不能及时完成签到,于是我一开始想做一个学习通自动完成手势签到的小程序。这个问题我一开始认为有两种方案可以解决:1.利用计算机视觉,解析老师的手势图。2.看看学习通学生PC端的手势签到页面中有没有隐藏着手势签到点位的信息,利用爬虫获取这些点位的信息,再利用pyautogui库自动操作鼠标,实现手势签到。

对于第一个方案,首先学生端是没有手势签到的界面,所以只能通过拍照的方式获取手势图信息。但这样清晰度肯定大打折扣。我还问了DeepSeek,它告诉我只提供最终静态手势图利用计算机视觉技术也无法准确反解出路径

对于第二个方案,我询问了DeepSeek,他让我打开学习通手势签到页面的html代码,让我观察在用户做出一次手势签到时有没有一些变量,例如名为ges、gesture之类的变量发生改变。我发现并没有。我又上网查询了资料,得到的结论是学习通会把这些信息“隐藏”起来,所以这个方案也不行。
但我还是不死心,我发现Github上是有相关的项目的,不过都是一个工程的代码量,一个项目就包含二十多个py程序,让我开始打退堂鼓。我让DeepSeek给我介绍一下这些项目的流程。它告诉我,这些基本都是伪造合法请求直接提交服务器来实现的。核心步骤在于:1. 逆向分析接口(抓包获取关键参数,如课程与活动、手势参数)2.硬编码预定义路径 对于只学过C语言和数据结构的小白来说,感觉过于吃操作了,毕竟没有相关的知识储备,最终还是选择不做这个项目QAQ 。

之后,面对python最后一节课的抢答环节,我又想做个学习通自动抢答程序,我想着,用socket技术实时监听请求,若监听到准备抢答的请求,便直接发送请求给学习通,自动完成抢答。理论上应该是可行的,但花数小时研究前人的项目以后,我最终还是放弃了……(知识储备还是太重要了)。

不过我会在这个暑假好好学习这些项目,一定让下届学弟能用上我的程序,不必再为手势签到头疼o( ̄▽ ̄)ブ,也能快速完成抢答!

最后,我选择了一个操作难度比较小的项目,学习通自动刷课小程序

(2)需求分析

每学期的思政课都有学习通观看视频的要求。这些视频数量多,时间短。如果单纯挂在电脑上刷,还要经常关注观看的进度,需手动切换下一个视频,效率很低。于是我便想,如果计算机能模拟人的操作,不就能解放双手,可以专心地在峡谷里打两把王者,冲一冲国标亚瑟了!(bushi)

二、实验内容

(1)创建一个自动化程序,使用 Python 编程实现对学习平台任务点的自动识别与操作

(2)程序能够自动识别屏幕上的任务点(如暂停按钮、多选题、单选题等),并根据任务类型完成相应操作;还能实现页面滚动和翻页功能;同时支持键盘监听功能,用户可随时按下 ESC 键停止程序

(3)包含文件的基本操作,例如加载配置文件、读取图像文件

(4)程序代码托管到码云

三、 实验过程

(1)重点解析

一、需要安装的库

  1. pyautogui:用于屏幕操作(如点击、滚动等)。
  2. keyboard:用于监听键盘事件(如检测 ESC 键)。
  3. OpenCV:提供confidence参数。

二、重要函数及其功能

  1. 负责点击图像的函数。与已存储的图像文件作对比,定位屏幕上的指定图像并点击其位置。
def click_image(image_name, confidence=CONFIDENCE):

"""点击指定名称的图像"""

image_path = f"{IMAGE_DIR}{image_name}.png"

try:

# 尝试在屏幕上定位指定的图像

image_location = pyautogui.locateOnScreen(image_path, confidence=confidence)

if image_location:

# 点击图像所在位置

pyautogui.click(image_location)

return True

return False

except Exception as e:

print(f"点击图像 {image_name} 失败: {str(e)}")

return False

用来对比的图像文件:

  1. 处理单选题函数。这里运用的逻辑是最笨的枚举,即一个个试(报告后续有升级方案,详见第六部分之实验拓展)。
def handle_single_choice():

"""处理单选题"""

# 尝试所有选项

click_image("A")

smart_sleep(1)

click_image("tj")

if click_image("hdcw"): # 如果有错误提示

for option in ['B', 'C', 'D']:

if not click_image(option):

break

smart_sleep(1)

click_image("tj")

if not click_image("hdcw"): # 如果没有错误提示,说明答案正确

return True

else:

return True

return False
  1. 处理任务点完成后的函数。包括检查未完成任务点、翻页或滚动页面以加载新任务点。
def handle_task_completion():

"""处理任务点完成后的操作"""

smart_sleep(1)

# 检查是否还有未完成的任务点

if click_image("pause") or click_image("dxt") or click_image("A"):

return True

# 检查是否已出现"下一页"按钮

if click_image("next"):

click_image("next")

smart_sleep(NEXT_PAGE_WAIT) # 等待新页面加载

return True

# 向下滚动直到检测到"下一页"或达到最大滚动次数

scroll_attempts = 0

while scroll_attempts < MAX_SCROLL_ATTEMPTS:

# 滚动页面

pyautogui.scroll(SCROLL_AMOUNT)

smart_sleep(SCROLL_WAIT_TIME)

# 检查滚动后是否出现新任务点

if click_image("pause") or click_image("dxt") or click_image("A"):

return True

# 检查滚动后是否出现"下一页"按钮

if click_image("next"):

click_image("next")

smart_sleep(NEXT_PAGE_WAIT) # 等待新页面加载

return True

scroll_attempts += 1

return False
  1. 键盘监听函数。可以让用户按下Esc关闭程序。
def setup_keyboard_listener():

"""设置键盘监听器"""

print("程序已启动,按 ESC 键可随时停止程序...")

keyboard.add_hotkey('esc', stop_program)

  

# 全局运行状态

running = True

  

def stop_program():

"""停止程序运行"""

global running

running = False

print("\n正在停止程序,请稍候...")
  1. 主函数。重点在于当识别到不同图像时,调用不同的函数。
def main():

global running

print_usage_guide() # 打印操作指南

a=input("按1后程序将执行...") # 等待用户确认

if a=='1':

# 最小化vscode

pyautogui.click(2234, 0)

print("程序启动,最小化vscode")

smart_sleep(0.6)

# 设置键盘监听

setup_keyboard_listener()

while running:

if click_image("zjcs"): # 到章末的章节检测时停止

print("检测到章节测试,停止运行")

break

while click_image("task") and running:

if click_image("pause"): # 当检测到任务点,就去找pause按钮

while not detect_image("jieshu1"):

smart_sleep(10)

continue

if not click_image("pause") :

pyautogui.scroll(-250) # 如果没找到pause按钮,就代表是页面不够下去

  

if click_image("dxt") and running: # 当识别到多选题的字眼时

handle_multi_choice()

elif click_image("A") and running: # 识别到视频中跳出有A选项的选择题

handle_single_choice()

elif click_image("bf") and running: # 中途视频意外暂停时对播放按钮进行点击

while not detect_image("jieshu1"):

smart_sleep(10)

continue

  

if running and detect_image("finish"): # 识别到任务点已完成的图片

handle_task_completion()

elif running:

# 等待一段时间再继续检测

handle_task_completion()

smart_sleep(0.6)

print("程序安全停止")

  

if __name__ == "__main__":

try:

main()

except Exception as e:

print(f"程序发生异常: {str(e)}")

finally:

# 清理键盘监听

keyboard.unhook_all()

input("按Enter键退出...")

(2)补充说明

  1. 从用户的角度出发,本程序还做了一个操作指南及温馨提示
def print_usage_guide():

"""打印操作指南及温馨提示"""

guide = """

操作指南:

1. 启动程序后,vscode将处于最小化状态。

2. 确保已进入任务章节界面,程序会自动识别并点击相应的按钮。

3. 按 ESC 键可以随时停止程序。

温馨提示:

1. 本程序在小概率情况下会识别不出对应的图像,建议每隔2小时查看一次刷课情况

2. 如果程序无法正常运行,请前往 auto_learn_config.json 检查配置文件是否正确,或手动调整图像识别的置信度。

3. 在最小化vscode代码处,如何根据机子修改坐标鼠标位置?详见实验报告~

"""

print(guide)
  1. 为了其他同学能真正用上我的程序,我创建了配置文件,方便修改程序参数,提高灵活性和可维护性
def load_config():

"""加载配置文件,如果不存在则创建默认配置"""

config_path = "auto_learn_config.json"

# 默认配置

default_config = {

"image_dir": "D:/Administrator/Documents/zzhFile/Study/Python/last/auto/cmp/",

"sleep_time": 0.6,

"multi_choice_sequences": [

["A", "B", "C", "D"],

["A", "B", "C"],

["A", "B"],

["A", "C"],

["A", "D"],

["B", "C", "D"],

["B", "C"],

["B", "D"],

["C", "D"]

],

"confidence": 0.5,

"scroll_amount": -300,

"max_scroll_attempts": 2,

"scroll_wait_time": 1,

"next_page_wait": 2

}

try:

# 如果配置文件存在则加载

if os.path.exists(config_path):

with open(config_path, 'r') as f:

config = json.load(f)

print("配置加载成功")

return config

except Exception as e:

print(f"加载配置失败: {e}")

# 创建默认配置文件

try:

with open(config_path, 'w') as f:

json.dump(default_config, f, indent=4)

print("创建默认配置文件")

return default_config

except Exception as e:

print(f"创建配置文件失败: {e}")

return default_config

  

# 加载配置

config = load_config()

IMAGE_DIR = config["image_dir"]

SLEEP_TIME = config["sleep_time"]

MULTI_CHOICE_SEQUENCES = config["multi_choice_sequences"]

CONFIDENCE = config["confidence"]

SCROLL_AMOUNT = config["scroll_amount"]

MAX_SCROLL_ATTEMPTS = config["max_scroll_attempts"]

SCROLL_WAIT_TIME = config["scroll_wait_time"]

NEXT_PAGE_WAIT = config["next_page_wait"]
  1. 获取最小化图标坐标位置的简单程序
import pyautogui

while True:

# 实时输出鼠标坐标

print(pyautogui.position())

pyautogui.sleep(1) # 每秒更新一次

(3)程序代码托管至码云

四、实验结果

(1) 终端开始运行的界面:

(2)运行的视频:

以下视频除了看进度条进度,以及拖动进度条进度是由我来操作(为了不把演示时间浪费在播放视频上),其他皆是由py自动化操作!(老师可开2倍速观看)

遗憾的是,对于部分“播放”及“下一页”的图标,程序有时无法识别到,导致效果没有那么好。

五、实验过程中遇到的问题和解决方案

问题一: 因为本实验鼠标会被程序操纵,一开始不知道该如何优雅地关闭程序
解决方案: 一开始我想做个图形化界面,用户可以凭借着sleep_time的那零点几秒按下“结束程序”的按钮。但是这还是太吃用户操作了。我后来便想到用键盘来监控。当用户按下Esc键时,便可以自如地关闭程序,无需用到鼠标。

问题二: 一开始我未设置配置文件,导致我在修改函数参数时效率极其低下。
解决方案: DeepSeek给了我建议,让我设置 JSON 配置文件统一管理函数参数

六、实验拓展

遗憾的是,我包括其他同学本学期学习通上并没有观看视频到一半,需要回答题目的课程。于是便不能验证我回答单选题和多选题的代码。但是我也并没有就此收手。因为原程序的逻辑是一个个试,试到正确答案为止,但效率太低了。我便想借助ai的帮助,帮我回答问题,直接得到正确答案。
我的设想是:当py识别到单选题、多选题的字段时,自动截图发送给DeepSeek,再让其根据图中信息回答。此时电脑在询问DeepSeek的提示词应为“只告诉我最终答案(选项)”,这样得到的结果便很简洁。此时,再根据答案选项点击相应图片位置就够了。
以下是我让DeepSeek本尊写的一段代码(逻辑上可行,但还未经验证):

(1)智能截图

def capture_question_area():

"""智能截取题目区域"""

# 获取屏幕尺寸

screen_width, screen_height = pyautogui.size()

# 题目区域坐标(可根据实际界面调整)

question_area = (

int(screen_width * 0.2), # left

int(screen_height * 0.3), # top

int(screen_width * 0.8), # right

int(screen_height * 0.6) # bottom

)

# 截取并保存题目图片

timestamp = int(time.time())

image_path = f"{SCREENSHOT_DIR}question_{timestamp}.png"

ImageGrab.grab(bbox=question_area).save(image_path)

log_action(f"截图保存至 {image_path}")

return image_path

(2)从DeepSeek处获得答案

def get_answer_from_deepseek(image_path):

"""使用DeepSeek API获取答案"""

if not ENABLE_DEEPSEEK or not API_KEY or API_KEY == "YOUR_API_KEY_HERE":

log_action("DeepSeek API未启用或未配置API密钥", False)

return None

try:

# 读取并编码图片

with open(image_path, "rb") as image_file:

encoded_image = base64.b64encode(image_file.read()).decode('utf-8')

# API请求参数

api_url = "https://api.deepseek.com/chat"

headers = {

"Authorization": f"Bearer {API_KEY}",

"Content-Type": "application/json"

}

payload = {

"model": "deepseek-chat",

"messages": [

{

"role": "user",

"content": [

{"type": "text", "text": "请根据题目图片给出正确答案选项(只需字母,如A或AB),不要解释"},

{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{encoded_image}"}}

]

}

],

"temperature": 0.1

}

response = requests.post(api_url, json=payload, headers=headers, timeout=10)

response.raise_for_status()

response_data = response.json()

# 解析AI返回的答案

answer_text = response_data['choices'][0]['message']['content']

# 提取答案选项(如A、B、AB等)

options = ''.join(re.findall(r'[A-D]', answer_text.upper()))

log_action(f"DeepSeek返回答案: {options}")

return options

except Exception as e:

log_action(f"获取DeepSeek答案失败: {str(e)}", False)

return None

七、课程感悟及总结

从一个只会print("人生苦短,我用python")的小白,到现在能扯出这么一大段代码的大黑,感谢王志强老师把我领进python门之中。一开始是听说python这门课给分高,加上对编程比较感兴趣才选的python。上课以后,就发现老师上课幽默风趣,讲起知识点来生动形象,言简意赅(蛋炒饭和西红柿炒鸡蛋的比喻估计这辈子都忘记不了)。并且还会预留相当多的时间给同学自主学习及探索。我非常喜欢这样的上课风格o( ̄▽ ̄)ブ。到后来,课前复杂的手势签到也让我觉得您非常有趣。总之,王老师是个很赞的老师!

而在本学期python课程中,我学习到了python的基本语法,四种序列(元组、字典、集合、列表)的差别及用法,函数的用法,字符串的语法及用法,类和方法,爬虫(爬虫写得好,XX进得早),socket技术(通信实验真的有意思),异常处理(除以0会怎么样?),数据库等等,收获颇多!

不过俗话说得好,“师傅领进门,修行靠个人”,在做实验四时,我看了大量的项目,发现老师讲的知识仅仅是冰山一角,python用途、用法都非常的广泛。而在想深入学习只能靠自己去学习探索。在学习过程中,我深感AI的强大,以及“学中做,做中学”的深刻内涵。AI能帮助一个小白快速地帮你拆解任务,搭好代码框架。即使一开始看不会,再让AI逐行给你解释,你就能慢慢理解。而在这从不会到会,也就是在“做中学”,这样的记忆非常深刻,学习效果也挺好的。

完成这个大实验,花了我至少二十多个小时。因为前面都在想着如何完成自动手势签到及自动抢答,便尝试学习,自己做一个。奈何知识储备还不够,即使在AI的帮助下我也很难完成这两个项目,最后就放弃了……不过,这个暑假就能好好系统地学习钻研了!一定可以让下届学弟学妹用上我的程序!

而最后完成的这个作品,只能说是差强人意。一些效果还没有实现得特别好,比如对于一些“暂停”“下一页”还不能精准识别,判断的逻辑不知道哪里有点小问题,导致实现起来有一点小奇怪。不过还是学习到很多新知识,完成作品以后还是非常有成就感的!

感恩王老师一学期的辛勤付出!“人生需要Python,更需要Passion!”

八、参考资料

(1)王志强老师学习通word文档
(2)# 签到#后台抓包#绕过前端验证#签到解析 ----CSDN
(3)超星学习通自动完成签到项目 ----Github
(4)学习通在线自动签到系统技术分析
(5)记录一次学习通接口分析 ----CSDN
(6)selenium和pyautogui实现简单的学习通自动化刷课

九、源代码

import pyautogui

import time

import json

import os

import keyboard

import sys

  

# --------------------- 操作指南 ---------------------

def print_usage_guide():

"""打印操作指南"""

guide = """

操作指南:

1. 启动程序后,vscode将处于最小化状态。

2. 确保已进入任务章节界面,程序会自动识别并点击相应的按钮。

3. 按 ESC 键可以随时停止程序。

温馨提示:

1. 本程序在小概率情况下会识别不出对应的图像,建议每隔2小时查看一次刷课情况

2. 如果程序无法正常运行,请前往 auto_learn_config.json 检查配置文件是否正确,或手动调整图像识别的置信度。

3. 在最小化vscode代码处,如何根据机子修改坐标鼠标位置?详见实验报告~

"""

print(guide)

  

# --------------------- 配置管理 ---------------------

def load_config():

"""加载配置文件,如果不存在则创建默认配置"""

config_path = "auto_learn_config.json"

# 默认配置

default_config = {

"image_dir": "D:/Administrator/Documents/zzhFile/Study/Python/last/auto/cmp/",

"sleep_time": 0.6,

"multi_choice_sequences": [

["A", "B", "C", "D"],

["A", "B", "C"],

["A", "B"],

["A", "C"],

["A", "D"],

["B", "C", "D"],

["B", "C"],

["B", "D"],

["C", "D"]

],

"confidence": 0.75,

"scroll_amount": -100,

"max_scroll_attempts": 2,

"scroll_wait_time": 1,

"next_page_wait": 2

}

try:

# 如果配置文件存在则加载

if os.path.exists(config_path):

with open(config_path, 'r') as f:

config = json.load(f)

print("配置加载成功")

return config

except Exception as e:

print(f"加载配置失败: {e}")

# 创建默认配置文件

try:

with open(config_path, 'w') as f:

json.dump(default_config, f, indent=4)

print("创建默认配置文件")

return default_config

except Exception as e:

print(f"创建配置文件失败: {e}")

return default_config

  

# 加载配置

config = load_config()

IMAGE_DIR = config["image_dir"]

SLEEP_TIME = config["sleep_time"]

MULTI_CHOICE_SEQUENCES = config["multi_choice_sequences"]

CONFIDENCE = config["confidence"]

SCROLL_AMOUNT = config["scroll_amount"]

MAX_SCROLL_ATTEMPTS = config["max_scroll_attempts"]

SCROLL_WAIT_TIME = config["scroll_wait_time"]

NEXT_PAGE_WAIT = config["next_page_wait"]

  

# --------------------- 核心功能 ---------------------

def click_image(image_name, confidence=CONFIDENCE):

"""点击指定名称的图像"""

image_path = f"{IMAGE_DIR}{image_name}.png"

try:

# 尝试在屏幕上定位指定的图像

image_location = pyautogui.locateOnScreen(image_path, confidence=confidence)

if image_location:

# 点击图像所在位置

pyautogui.click(image_location)

return True

return False

except Exception as e:

print(f"点击图像 {image_name} 失败: {str(e)}")

return False

  

def smart_sleep(seconds=SLEEP_TIME):

"""智能等待"""

time.sleep(seconds)

  

def detect_image(image_name, confidence=CONFIDENCE):

"""检测指定名称的图像是否存在"""

image_path = f"{IMAGE_DIR}{image_name}.png"

try:

# 尝试在屏幕上定位指定的图像

image_location = pyautogui.locateOnScreen(image_path, confidence=confidence)

if image_location:

print(f"检测到图像: {image_name},位置: {image_location}")

return True # 返回检测结果

return False

except Exception as e:

print(f"检测图像 {image_name} 失败: {str(e)}")

return False

  

def handle_multi_choice():

"""处理多选题"""

for sequence in MULTI_CHOICE_SEQUENCES:

for option in sequence:

smart_sleep()

click_image(option)

click_image("tj")

if not click_image("hdcw"): # 如果没有错误提示,说明答案正确

return True

return False

  

def handle_single_choice():

"""处理单选题"""

# 尝试所有选项

click_image("A")

smart_sleep(1)

click_image("tj")

if click_image("hdcw"): # 如果有错误提示

for option in ['B', 'C', 'D']:

if not click_image(option):

break

smart_sleep(1)

click_image("tj")

if not click_image("hdcw"): # 如果没有错误提示,说明答案正确

return True

else:

return True

return False

  

def handle_task_completion():

"""处理任务点完成后的操作"""

smart_sleep(0.6)

# 检查是否还有未完成的任务点

if click_image("dxt") or click_image("A") or click_image("task"):

return True

# 检查是否已出现"下一页"按钮

if click_image("next"):

click_image("next")

smart_sleep(NEXT_PAGE_WAIT) # 等待新页面加载

return True

if click_image("next2"):

click_image("next2")

smart_sleep(NEXT_PAGE_WAIT) # 等待新页面加载

return True

# 向下滚动直到检测到"下一页"或达到最大滚动次数

scroll_attempts = 0

while scroll_attempts < MAX_SCROLL_ATTEMPTS:

# 滚动页面

pyautogui.scroll(-400)

smart_sleep(0.3)

# 检查滚动后是否出现新任务点

if click_image("dxt") or click_image("A") or click_image("task"):

return True

# 检查滚动后是否出现"下一页"按钮

if click_image("next"):

click_image("next")

smart_sleep(NEXT_PAGE_WAIT) # 等待新页面加载

return True

if click_image("next2"):

click_image("next2")

smart_sleep(NEXT_PAGE_WAIT) # 等待新页面加载

return True

scroll_attempts += 1

return False

  

# --------------------- 键盘监听 ---------------------

def setup_keyboard_listener():

"""设置键盘监听器"""

print("程序已启动,按 ESC 键可随时停止程序...")

keyboard.add_hotkey('esc', stop_program)

  

# 全局运行状态

running = True

  

def stop_program():

"""停止程序运行"""

global running

running = False

print("\n正在停止程序,请稍候...")

  

# --------------------- 主程序 ---------------------

def main():

global running

print_usage_guide() # 打印操作指南

a=input("按1后程序将执行...") # 等待用户确认

if a=='1':

# 最小化vscode

pyautogui.click(2234, 0)

print("程序启动,最小化vscode")

smart_sleep(0.6)

# 设置键盘监听

setup_keyboard_listener()

while running:

if click_image("zjcs"): # 到章末的章节检测时停止

print("检测到章节测试,停止运行")

break

while click_image("task") and running:

if click_image("pause"): # 当检测到任务点,就去找pause按钮

while not detect_image("jieshu1"):

smart_sleep(10)

continue

if not click_image("pause") :

pyautogui.scroll(-250) # 如果没找到pause按钮,就代表是页面不够下去

  

if click_image("dxt") and running: # 当识别到多选题的字眼时

handle_multi_choice()

elif click_image("A") and running: # 识别到视频中跳出有A选项的选择题

handle_single_choice()

elif click_image("bf") and running: # 中途视频意外暂停时对播放按钮进行点击

while not detect_image("jieshu1"):

smart_sleep(10)

continue

  

if running and detect_image("finish"): # 识别到任务点已完成的图片

handle_task_completion()

elif running:

# 等待一段时间再继续检测

handle_task_completion()

smart_sleep(0.6)

print("程序安全停止")

  

if __name__ == "__main__":

try:

main()

except Exception as e:

print(f"程序发生异常: {str(e)}")

finally:

# 清理键盘监听

keyboard.unhook_all()

input("按Enter键退出...")
posted @ 2025-06-10 22:33  chow-  阅读(70)  评论(0)    收藏  举报