python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python学习通签到统计

基于Python开发的学习通签到数据统计工具

作者:.笑对人生.

本文介绍的项目是一款面向学习通签到数据的可视化统计工具,核心目标是解决学习通导出的签到表格手动统计效率低、易出错的问题,下面将详细讲解学习通签到统计工具开发过程以及Python代码完整实现,需要的朋友可以参考下

一、引言

本文介绍的项目是一款面向学习通签到数据的可视化统计工具,核心目标是解决学习通导出的签到表格手动统计效率低、易出错的问题。下面将详细讲解学习通签到统计工具开发过程以及Python代码完整实现。

二、第一阶段:需求分析与功能规划

开发前首先明确核心需求,确保工具贴合实际使用场景:

1. 核心业务需求

2. 非功能需求

三、第二阶段:技术选型

结合需求与开发成本,选择轻量、易部署的技术栈:

技术 / 库选型理由
Python 3.8+生态丰富,数据处理库成熟,跨平台,打包成 exe 后无需安装运行环境
tkinter + ttkPython 内置 GUI 库,无需额外安装,足够支撑轻量桌面应用;ttk 提升界面美观度
pandas高效处理表格数据,支持 Excel/CSV 读写、分组统计,是 Python 表格处理的首选库
openpyxlpandas 写入 Excel 的依赖库,支持.xlsx 格式,处理大数据量更稳定
os/pathlib处理文件路径、遍历文件夹,跨平台兼容
datetime生成日志时间戳,记录操作时间
warnings过滤 openpyxl 的样式警告,提升运行体验
pyinstaller(后续打包)将代码打包成 exe,方便非技术用户使用

四、第三阶段:架构设计

采用面向对象(OOP) 设计,将功能封装为AttendanceApp类,核心优势是 “数据与逻辑封装、代码复用、便于维护”。类的核心结构如下:

核心设计思路:

五、第四阶段:分模块详细开发

1. 初始化与基础配置(__init__方法)

def __init__(self, root):
    self.root = root
    self.root.title("学习通签到统计工具")
    self.root.geometry("900x700")  # 固定窗口大小,适配主流屏幕
    # 全局变量初始化:存储文件路径和汇总数据
    self.file_paths = []
    self.summary_data = []
    # 过滤openpyxl的样式警告(提升用户体验)
    warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')
    # 初始化UI
    self.create_widgets()

2. UI 组件开发(create_widgets 方法)

按 “功能分区” 构建 UI,确保布局清晰、操作符合用户习惯:

组件区域核心控件与作用
顶部说明区Label 控件:显示工具名称、功能说明,字体放大提升辨识度
按钮操作区Button 控件:导入文件(单 / 多 / 文件夹)、重置、处理文件、生成汇总表,按 “操作流程” 排列
文件列表区Listbox + Scrollbar:显示已导入文件,支持滚动查看,适配多文件场景
进度反馈区Progressbar + Label:显示处理进度、当前状态(如 “等待导入文件”)
日志记录区ScrolledText + Button:滚动日志框(禁用手动编辑)、清空日志按钮

关键实现细节:

3. 日志系统开发(log_message/clear_log 方法)

日志是工具的 “调试与追溯” 核心,设计要点:

def log_message(self, message, level="INFO"):
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")  # 精确到秒的时间戳
    log_entry = f"[{timestamp}] [{level}] {message}\n"  # 标准化日志格式
    # 临时启用日志框,写入内容后禁用
    self.log_text.config(state=tk.NORMAL)
    self.log_text.insert(tk.END, log_entry)
    self.log_text.see(tk.END)  # 自动滚动到最新日志
    self.log_text.config(state=tk.DISABLED)

4. 文件导入模块开发

解决 “如何便捷导入文件并过滤无效文件” 的问题,核心是 3 个导入方法 + 1 个辅助方法:

(1)临时文件过滤(is_temp_file)

学习通导出文件时可能生成临时文件(如~$xxx.xlsx),需过滤避免处理错误:

def is_temp_file(self, file_path):
    filename = os.path.basename(file_path)
    return filename.startswith('~$') or filename.startswith('.')  # 过滤Excel临时文件/隐藏文件

(2)单文件 / 批量 / 文件夹导入

关键设计:

5. 核心数据处理模块开发

这是工具的 “业务核心”,解决 “如何解析学习通文件并统计数据” 的问题,分为 3 个核心方法:

(1)动态查找表头行(find_header_row)

学习通导出的签到文件表头行不固定(可能第 1 行是标题,第 2 行是表头),需动态定位包含 “签到状态” 的行:

def find_header_row(self, df):
    for i, row in df.iterrows():
        if '签到状态' in str(row.values):  # 遍历每行,查找包含目标字段的行
            return i
    return None  # 未找到则返回None,后续抛出异常

(2)单个文件处理(process_single_file)

这是数据处理的核心,步骤拆解:

  1. 读取文件:根据后缀(xlsx/csv)选择pd.read_excel/read_csv,先读取全量数据找表头;
  2. 校验表头:若未找到 “签到状态” 行,抛出ValueError
  3. 重新读取 + 列名清理:以找到的表头行重新读取,清理列名空格(避免 “姓名” 和 “姓名” 被识别为不同列);
  4. 关键列校验:检查是否包含['姓名', '学号/工号', '签到状态']等必填列,缺失则抛出KeyError
  5. 多维度统计
# 已签统计:仅“已签”算1,其他状态算0
df['已签统计'] = df['签到状态'].apply(lambda x: 1 if str(x).strip() == '已签' else 0 if str(x).strip() in ['教师代签', '迟到', '未参与'] else None)
# 最终签到统计:已签/教师代签/迟到算1,未参与算0(核心统计维度)
df['最终签到统计'] = df['签到状态'].apply(lambda x: 1 if str(x).strip() in ['已签', '教师代签', '迟到'] else 0 if str(x).strip() == '未参与' else None)
  1. 保存统计结果
    • 优先保存到原文件同目录(命名为 “原文件名_统计结果.xlsx”);
    • 若原路径无权限(如文件在只读文件夹),弹出保存对话框让用户选择路径;
  2. 缓存汇总数据:将当前文件的统计结果存入summary_data,为后续汇总表做准备。

(3)批量处理文件(process_files)

实现 “批量处理 + 异常隔离 + 进度反馈”:

  1. 校验是否有导入文件,无则提示并返回;
  2. 初始化进度条(最大值为文件数量),重置汇总数据;
  3. 遍历所有文件,逐个调用process_single_file
  4. 异常处理:捕获PermissionError(文件被占用)、KeyError(列缺失)、通用Exception,记录失败数,不中断批量处理;
  5. 实时更新进度条(self.root.update_idletasks()强制刷新 UI);
  6. 处理完成后,通过弹窗 + 日志反馈 “成功 / 失败数量”。

6. 汇总表生成模块(generate_summary_button)

解决 “多文件按学生维度汇总” 的需求,核心逻辑:

  1. 校验summary_data是否为空(无处理结果则提示);
  2. 将汇总数据转为 DataFrame,按['姓名', '学号/工号', '学校', '院系', '专业', '行政班级']分组(确保学生唯一);
  3. 对所有统计列求和(agg({'已签统计':'sum', ...})),并重命名为 “总 XXX 统计”;
  4. 弹出保存对话框,将汇总结果保存为 Excel 文件;
  5. 反馈汇总结果(统计学生数量、保存路径)。

7. 重置功能(reset_all)

支持用户 “清空所有数据重新操作”,设计要点:

六、第五阶段:测试与优化

开发完成后,需覆盖多场景测试,修复问题并优化体验:

1. 核心测试场景

测试场景测试目的测试结果
导入单个 Excel/CSV 文件验证文件读取、表头查找、统计是否正确统计列生成正常,文件保存成功
导入临时文件(~$xxx.xlsx)验证临时文件过滤逻辑自动跳过,日志提示 “跳过临时文件”
导入缺失 “签到状态” 列的文件验证列校验、异常提示弹出错误提示,日志记录 KeyError
导入被占用的文件验证权限错误处理、保存对话框弹出提示权限错误,弹出保存对话框
批量导入 10 + 文件验证进度条、批量处理、异常隔离进度条实时更新,失败文件不影响其他
生成汇总表验证分组求和、列重命名、保存汇总数据正确,列名符合预期
重置所有数据验证数据清空、UI 重置所有数据 / UI 恢复初始状态

2. 关键优化点

七、学习通签到统计工具的Python代码完整实现

import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import pandas as pd
import os
from pathlib import Path
import datetime
import warnings

class AttendanceApp:
    def __init__(self, root):
        self.root = root
        self.root.title("学习通签到统计工具")
        self.root.geometry("900x700")

        # 全局变量
        self.file_paths = []
        self.summary_data = []

        # 过滤openpyxl的样式警告
        warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')

        # 创建UI组件
        self.create_widgets()

    def create_widgets(self):
        # 顶部说明
        ttk.Label(self.root, text="学习通签到统计工具", font=("微软雅黑", 16)).pack(pady=10)
        ttk.Label(self.root, text="支持CSV/Excel文件导入,自动统计签到次数").pack(pady=5)

        # 按钮区域
        button_frame = ttk.Frame(self.root)
        button_frame.pack(pady=10, fill=tk.X, padx=20)

        ttk.Button(button_frame, text="导入单个文件", command=self.import_single_file).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="批量导入文件", command=self.import_multiple_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="导入文件夹", command=self.import_folder).pack(side=tk.LEFT, padx=5)
        # 重置按钮
        ttk.Button(button_frame, text="重置所有", command=self.reset_all).pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="处理选中文件", command=self.process_files).pack(side=tk.RIGHT, padx=5)
        # padx参数移到pack方法中(ttk.Button初始化不支持padx)
        ttk.Button(button_frame, text="生成汇总表", command=self.generate_summary_button).pack(side=tk.RIGHT, padx=10)

        # 文件列表显示
        file_frame = ttk.Frame(self.root)
        file_frame.pack(pady=5, fill=tk.BOTH, expand=True, padx=20)

        ttk.Label(file_frame, text="已导入文件列表:").pack(anchor=tk.W)
        self.file_listbox = tk.Listbox(file_frame, height=8)
        self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 滚动条
        file_scroll = ttk.Scrollbar(file_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
        file_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        self.file_listbox.config(yscrollcommand=file_scroll.set)

        # 进度条
        self.progress = ttk.Progressbar(self.root, orient=tk.HORIZONTAL, length=400, mode='determinate')
        self.progress.pack(pady=5)

        # 状态标签
        self.status_label = ttk.Label(self.root, text="等待导入文件...")
        self.status_label.pack(pady=5)

        # 日志区域
        log_frame = ttk.Frame(self.root)
        log_frame.pack(pady=5, fill=tk.BOTH, expand=True, padx=20)

        ttk.Label(log_frame, text="操作日志:").pack(anchor=tk.W)
        self.log_text = scrolledtext.ScrolledText(log_frame, height=15, state=tk.DISABLED, wrap=tk.WORD)
        self.log_text.pack(fill=tk.BOTH, expand=True)

        # 清空日志按钮
        ttk.Button(log_frame, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, pady=5)

    def log_message(self, message, level="INFO"):
        """添加日志信息"""
        # 生成时间戳
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        # 格式化日志内容
        log_entry = f"[{timestamp}] [{level}] {message}\n"

        # 启用文本框,添加内容,然后禁用
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, log_entry)
        # 自动滚动到最后
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)

    def clear_log(self):
        """清空日志"""
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.config(state=tk.DISABLED)
        self.log_message("日志已清空", "INFO")

    def reset_all(self):
        """重置所有状态(重新统计)"""
        # 确认重置操作
        if messagebox.askyesno("确认重置", "是否确定重置所有数据?这将清空已导入的文件和统计数据!"):
            # 清空全局变量
            self.file_paths = []
            self.summary_data = []
            # 清空文件列表
            self.file_listbox.delete(0, tk.END)
            # 重置进度条
            self.progress['value'] = 0
            # 重置状态标签
            self.status_label.config(text="等待导入文件...")
            # 日志记录
            self.log_message("已重置所有数据:清空文件列表和汇总数据", "WARNING")
            messagebox.showinfo("重置完成", "所有数据已重置,可重新导入文件进行统计")

    def import_single_file(self):
        """导入单个文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("Excel文件", "*.xlsx"), ("CSV文件", "*.csv")]
        )
        if file_path and file_path not in self.file_paths and not self.is_temp_file(file_path):
            self.file_paths.append(file_path)
            self.update_file_list()
            self.log_message(f"成功导入单个文件:{os.path.basename(file_path)}")
        elif self.is_temp_file(file_path):
            self.log_message(f"跳过临时文件:{os.path.basename(file_path)}", "WARNING")

    def import_multiple_files(self):
        """批量导入多个文件"""
        files = filedialog.askopenfilenames(
            filetypes=[("Excel文件", "*.xlsx"), ("CSV文件", "*.csv")]
        )
        imported_count = 0
        skipped_count = 0
        for file in files:
            if file not in self.file_paths and not self.is_temp_file(file):
                self.file_paths.append(file)
                imported_count += 1
            elif self.is_temp_file(file):
                skipped_count += 1
        self.update_file_list()
        self.log_message(f"批量导入完成:成功导入{imported_count}个文件,跳过{skipped_count}个临时文件")

    def import_folder(self):
        """导入整个文件夹"""
        folder_path = filedialog.askdirectory()
        if folder_path:
            imported_count = 0
            skipped_count = 0
            for root, _, files in os.walk(folder_path):
                for file in files:
                    if file.endswith(('.xlsx', '.csv')):
                        file_path = os.path.join(root, file)
                        if file_path not in self.file_paths and not self.is_temp_file(file_path):
                            self.file_paths.append(file_path)
                            imported_count += 1
                        elif self.is_temp_file(file_path):
                            skipped_count += 1
            self.update_file_list()
            self.log_message(f"文件夹导入完成:从{folder_path}导入{imported_count}个文件,跳过{skipped_count}个临时文件")

    def is_temp_file(self, file_path):
        """判断是否是临时文件(包含~或隐藏文件)"""
        filename = os.path.basename(file_path)
        return filename.startswith('~$') or filename.startswith('.')

    def update_file_list(self):
        """更新文件列表显示"""
        self.file_listbox.delete(0, tk.END)
        for file in self.file_paths:
            self.file_listbox.insert(tk.END, os.path.basename(file))
        self.status_label.config(text=f"已导入 {len(self.file_paths)} 个文件")

    def process_files(self):
        """处理所有导入的文件(仅生成单个文件统计结果,不汇总)"""
        if not self.file_paths:
            messagebox.showwarning("警告", "请先导入文件!")
            self.log_message("处理文件失败:未导入任何文件", "ERROR")
            return

        self.progress['value'] = 0
        self.progress['maximum'] = len(self.file_paths)
        self.summary_data = []  # 重置汇总数据

        self.log_message(f"开始处理{len(self.file_paths)}个文件...")

        success_count = 0
        fail_count = 0

        for i, file_path in enumerate(self.file_paths):
            try:
                self.log_message(f"正在处理文件:{os.path.basename(file_path)}")
                self.process_single_file(file_path)
                self.progress['value'] = i + 1
                self.root.update_idletasks()
                success_count += 1
                self.log_message(f"文件处理成功:{os.path.basename(file_path)}")
            except PermissionError:
                fail_count += 1
                error_msg = f"权限错误:无法访问文件{os.path.basename(file_path)},请确保文件未被打开且有读写权限"
                messagebox.showerror("权限错误", error_msg)
                self.log_message(error_msg, "ERROR")
            except KeyError as e:
                fail_count += 1
                error_msg = f"列名错误:文件{os.path.basename(file_path)}缺少关键列{str(e)}"
                messagebox.showerror("列名错误", error_msg)
                self.log_message(error_msg, "ERROR")
            except Exception as e:
                fail_count += 1
                error_msg = f"处理文件{os.path.basename(file_path)}时出错:{str(e)}"
                messagebox.showerror("错误", error_msg)
                self.log_message(error_msg, "ERROR")

        self.progress['value'] = 0
        self.status_label.config(text=f"文件处理完成:成功{success_count}个,失败{fail_count}个")
        self.log_message(f"文件处理结束:成功{success_count}个,失败{fail_count}个")

        if success_count > 0:
            messagebox.showinfo("完成",
                                f"文件处理完成!成功{success_count}个,失败{fail_count}个\n可点击'生成汇总表'按钮创建汇总文件")

    def find_header_row(self, df):
        """动态查找包含'签到状态'的表头行"""
        for i, row in df.iterrows():
            if '签到状态' in str(row.values):
                return i
        return None

    def process_single_file(self, file_path):
        """处理单个签到文件"""
        # 读取整个文件,用于动态检测表头
        if file_path.endswith('.xlsx'):
            full_df = pd.read_excel(file_path, header=None)
        elif file_path.endswith('.csv'):
            full_df = pd.read_csv(file_path, header=None)

        # 动态查找表头行
        header_row = self.find_header_row(full_df)
        if header_row is None:
            raise ValueError("未找到包含'签到状态'的表头行,请确认文件格式")

        # 用找到的表头行重新读取文件
        if file_path.endswith('.xlsx'):
            df = pd.read_excel(file_path, header=header_row)
        elif file_path.endswith('.csv'):
            df = pd.read_csv(file_path, header=header_row)

        # 清理列名中的空格和特殊字符
        df.columns = [col.strip() for col in df.columns]

        # 验证关键列是否存在
        required_columns = ['姓名', '学号/工号', '学校', '院系', '专业', '行政班级', '签到状态']
        for col in required_columns:
            if col not in df.columns:
                raise KeyError(col)

        # 生成已签统计
        df['已签统计'] = df['签到状态'].apply(
            lambda x: 1 if str(x).strip() == '已签' else 0 if str(x).strip() in ['教师代签', '迟到', '未参与'] else None
        )
        # 生成教师代签统计
        df['教师代签统计'] = df['签到状态'].apply(
            lambda x: 1 if str(x).strip() == '教师代签' else 0 if str(x).strip() in ['已签', '迟到', '未参与'] else None
        )
        # 生成迟到统计
        df['迟到统计'] = df['签到状态'].apply(
            lambda x: 1 if str(x).strip() == '迟到' else 0 if str(x).strip() in ['已签', '教师代签', '未参与'] else None
        )
        # 生成未参与统计
        df['未参与统计'] = df['签到状态'].apply(
            lambda x: 1 if str(x).strip() == '未参与' else 0 if str(x).strip() in ['已签', '教师代签', '迟到'] else None
        )
        # 生成最终签到统计(已签/教师代签/迟到都算1,未参与算0)
        df['最终签到统计'] = df['签到状态'].apply(
            lambda x: 1 if str(x).strip() in ['已签', '教师代签', '迟到'] else 0 if str(x).strip() == '未参与' else None
        )

        # 保存处理后的文件
        try:
            output_path = f"{os.path.splitext(file_path)[0]}_统计结果.xlsx"
            df.to_excel(output_path, index=False)
            self.log_message(f"统计文件已保存:{os.path.basename(output_path)}")
        except PermissionError:
            # 如果原路径无法写入,让用户选择保存位置
            self.log_message(f"原路径无写入权限,弹出保存对话框:{os.path.basename(file_path)}", "WARNING")
            output_path = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                initialfile=f"{os.path.splitext(os.path.basename(file_path))[0]}_统计结果.xlsx",
                filetypes=[("Excel文件", "*.xlsx")]
            )
            if output_path:
                df.to_excel(output_path, index=False)
                self.log_message(f"统计文件已保存到指定位置:{os.path.basename(output_path)}")
            else:
                raise PermissionError("用户取消了文件保存操作")

        # 收集汇总数据(包含所有需要的字段)
        file_name = os.path.basename(file_path)
        for _, row in df.iterrows():
            self.summary_data.append({
                '姓名': row['姓名'],
                '学号/工号': row['学号/工号'],
                '学校': row['学校'],
                '院系': row['院系'],
                '专业': row['专业'],
                '行政班级': row['行政班级'],
                '文件名': file_name,
                '已签统计': row['已签统计'],
                '教师代签统计': row['教师代签统计'],
                '迟到统计': row['迟到统计'],
                '未参与统计': row['未参与统计'],
                '最终签到统计': row['最终签到统计']
            })

    def generate_summary_button(self):
        """独立的汇总表生成按钮处理函数"""
        if not self.summary_data:
            messagebox.showwarning("警告", "暂无汇总数据!请先处理文件")
            self.log_message("生成汇总表失败:暂无汇总数据", "WARNING")
            return

        self.log_message("开始生成汇总表...")

        try:
            summary_df = pd.DataFrame(self.summary_data)
            # 按分组对所有统计列求和
            final_summary = summary_df.groupby(
                ['姓名', '学号/工号', '学校', '院系', '专业', '行政班级']
            ).agg({
                '已签统计': 'sum',
                '教师代签统计': 'sum',
                '迟到统计': 'sum',
                '未参与统计': 'sum',
                '最终签到统计': 'sum'
            }).reset_index()

            # 重命名列名为"总XXX统计"
            final_summary.rename(columns={
                '已签统计': '总已签统计',
                '教师代签统计': '总教师代签统计',
                '迟到统计': '总迟到统计',
                '未参与统计': '总未参与统计',
                '最终签到统计': '总最终签到统计'
            }, inplace=True)

            # 保存汇总表
            save_path = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                initialfile="学习通签到汇总表.xlsx",
                filetypes=[("Excel文件", "*.xlsx")]
            )

            if save_path:
                final_summary.to_excel(save_path, index=False)
                self.log_message(f"汇总表生成成功:{os.path.basename(save_path)}")
                self.log_message(f"汇总数据:共统计{len(final_summary)}名学生的签到情况,包含多维度签到统计")
                messagebox.showinfo("成功",
                                    f"汇总表已保存!\n文件路径:{save_path}\n共统计{len(final_summary)}名学生\n包含:总已签、总教师代签、总迟到、总未参与、总最终签到统计")
            else:
                self.log_message("用户取消了汇总表保存操作", "WARNING")

        except Exception as e:
            error_msg = f"生成汇总表时出错:{str(e)}"
            messagebox.showerror("错误", error_msg)
            self.log_message(error_msg, "ERROR")

if __name__ == "__main__":
    root = tk.Tk()
    app = AttendanceApp(root)
    root.mainloop()

八、程序运行部分截图展示

九、打包项目

步骤 1:创建并激活纯净虚拟环境打开 Anaconda Prompt,执行以下命令(复制粘贴即可)

# 1. 创建仅包含Python 3.10的纯净环境(兼容性最佳,适配tkinter/pandas)
conda create -n attendance_tool python=3.10 -y
 
# 2. 激活该环境
conda activate attendance_tool
 
# 3. 仅安装必需依赖(无任何冗余,清华源加速)
pip install pandas openpyxl pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple/

步骤 2:切换目录并快速打包

# 切换到你的学习通签到工具代码所在文件夹(替换为实际路径)
cd C:\Users\ABC\PycharmProjects\AttendanceTool
 
# 用目录模式打包(-D),速度快、体积小、稳定性高;-w隐藏控制台(GUI工具必备)
pyinstaller -w -n 学习通签到统计工具 -D 学习通签到统计工具.py

步骤 3:验证结果

打包时间:仅需 1-2 分钟即可完成;
exe 位置:dist / 学习通签到统计工具 / 目录下的学习通签到统计工具.exe;
体积:约 60-90MB(对比全局环境打包的 1GB+,大幅精简);
功能:双击 exe,测试文件导入、签到统计、汇总表生成等核心功能,和原代码完全一致。

十、总结

本文介绍了一款基于Python开发的学习通签到数据统计工具,该工具通过GUI界面实现高效自动化处理,主要解决手动统计Excel/CSV签到表格效率低、易出错的问题。工具核心功能包括:支持单文件/多文件/文件夹导入;自动识别动态表头;多维度统计签到状态(已签、教师代签、迟到、未参与);生成带统计列的单个文件和汇总表。采用tkinter+ttk构建界面,pandas处理数据,具有异常处理、日志记录和进度反馈机制。经过测试验证,工具能准确处理学习通导出的各类签到文件,并通过pyinstaller打包为60-90MB的独立exe程序,方便非技术用户使用。

以上就是基于Python开发的学习通签到数据统计工具的详细内容,更多关于Python学习通签到统计的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文