学生上交DOC、XLS作业并统计未上交学生

50 阅读2分钟

学生上交作业并统计

项目介绍

一款基于Python语言开发的学生作业上交统计工具,专为内网环境下学生上机操作,无纸化交作业,收集后自动统计。

解决日常作业,N多学生,不知道谁没交,日常统计平时分记录。

背景与价值

痛点

  1. 学生在上交作业时,总有学生不按你 的要求来做,如要求文档的名称是“学号+姓名”,那你收到 情况可能如下:
    1. 中间加空格“1 王小二“
    2. 学号放后面 “王小二 1”
    3. 加其它文号 “2号 王小二”
    4. 收集文档时 应用会增加一些前缀,如“$admin_”
  2. 统计学生的日常分时,需要一个个的核对,很不方便。

目标

  • 检查按上交的目录,生成对应的学生清单
  • 默认学号是连续的。对学号 不连续 的,进行补位,并标注“未按要求上交作业”
  • 生成EXCEL文档,以方便后续日常分 的统计。

适用场景

  • 不方便使用在线作业系统,需要现场收集DOC或XLS的场景

核心功能

  • 通过读取当前文件夹下的所有doc/docx/xls/xlsx文件,排除文件夹。
  • 通过读取文件名称,通过用RE正则表达式,过滤不符合的文件名。
  • 清理后的文件名,按规则生成学号 和 姓名。
  • 按最大学号,补齐中间缺失学号,并标记“未按要求上交作业”
  • 按文件夹名称生成EXCEL,方便汇总。

技术特点

  • 无中间件依赖(打包好的运行文件)
  • 轻量化设计(脚本用jupyter-notebook,可分段测试)

快速开始

安装:使用UV安装 同步现有依赖 uv sync

文件名要以学号+姓名来修改,用来获取。

结果展示

image.png

原始文档

image.png

引用包代码

import re
import pandas as pd
from pathlib import Path
from typing import List

读取文件名

def readFilesName(folder_path: str, endswith: List[str], istartswith: List[str] = ['`', '~']) -> List[str]:
    """
    读取指定文件夹中符合后缀条件且不以后缀列表开头的文件名
    参数:
        folder_path: 文件夹路径
        endswith: 需要匹配的文件后缀列表(如 ['doc', 'docx'])
        istartswith: 需要排除的文件名开头字符列表
    返回:
        符合条件的文件名列表
    """
    # 转换为Path对象
    folder = Path(folder_path)
    
    # 检查文件夹是否存在
    if not folder.exists() or not folder.is_dir():
        print(f"错误:文件夹 '{folder_path}' 不存在或不是目录")
        return []
    
    # 统一处理后缀(确保以.开头)
    suffixes = [f".{s}" if not s.startswith('.') else s for s in endswith]
    # 转为小写后缀(用于不区分大小写匹配)
    suffixes_lower = [s.lower() for s in suffixes]
    
    # 筛选符合条件的文件
    word_files = []
    for file in folder.iterdir():
        # 判断是否为文件
        if file.is_file():
            # 文件名小写处理(不区分大小写匹配后缀)
            file_suffix = file.suffix.lower()
            # 检查后缀是否匹配
            if file_suffix in suffixes_lower:
                # 检查文件名开头是否需要排除
                if file.name[0] not in istartswith:
                    word_files.append(file.name)
    
    if not word_files:
        print(f"在文件夹 '{folder_path}' 中未找到符合条件的文件")
    return word_files

文件名处理

def idNameInfo(file_name: str, special_chars=r'[.,;:\-*/\\]')->dict:
    """
    从文件名中提取学号、姓名信息,并返回结构化字典。
    
    功能特点:
    1. 去除特殊字符及其后面的内容
    2. 去除以_结尾的前缀(字母或符号开头,可包含数字)
    3. 去除"号"字
    4. 支持多种格式的学号和姓名提取
    
    参数:
    file_name: str - 输入的文件名
    special_chars: str - 需要去除的特殊字符集,默认为常见标点符号
    
    返回:
    dict - 包含学号、姓名和成绩的字典,如果无法提取则返回空字典
    """
    # 去除特殊字符及其后面的内容
    cleaned = re.sub(f'{special_chars}.*', '', file_name.strip())
    if not cleaned:  # 清理后为空则直接返回
        return {}
    
    # 新增功能1:去除以_结尾的前缀(字母或符号开头,可包含数字,以_结尾)
    # 匹配以字母或符号开头,可包含数字,以_结尾的前缀,不包含纯数字前缀
    # 使用否定前瞻确保不是纯数字前缀
    prefix_pattern = re.compile(r'^(?!\d+$)[a-zA-Z0-9\!\@\#\$\%\^\&\*\(\)\-\_\+\=\[\]\{\}\|\\\;\:\'\"\,\.\<\>\/\?]+_')
    cleaned = prefix_pattern.sub('', cleaned)
    
    # 新增功能2:去除"号"字
    cleaned = cleaned.replace('号', '')
    
    # 检查是否包含"答案"关键字
    if re.search(r'答案', cleaned):
        return {"学号": 0, "姓名": "答案", "成绩": ""}
    
    # 匹配多种格式的学号和姓名
    pattern = re.compile(
        r'^(?P<id>\d+)(?P<name>[\u4e00-\u9fa5a-zA-Z\s]+)$|'  # 学号在前(纯数字)
        r'^(?P<name2>[\u4e00-\u9fa5a-zA-Z\s]+)(?P<id2>\d+)$|'  # 名字在前,学号在后
        r'^(?P<id3>\d+_\d+)(?P<name3>[\u4e00-\u9fa5a-zA-Z\s]+)$|'  # 学号包含下划线(如123_45)
        r'^.*?_?(?P<id4>\d+)(?P<name4>[\u4e00-\u9fa5a-zA-Z\s]+)$'  # 处理剩余的数字学号情况
    )
    
    match = pattern.match(cleaned.strip())
    if match:
        # 提取分组内容(多种情况取其一)
        student_id = match.group('id') or match.group('id2') or match.group('id3') or match.group('id4')
        name = match.group('name') or match.group('name2') or match.group('name3') or match.group('name4')
        
        # 如果学号包含下划线,保持原样(不转换为int)
        if student_id and name:
            if '_' in str(student_id):
                return {"学号": student_id, "姓名": name.strip(), "成绩": ""}
            else:
                return {"学号": int(student_id), "姓名": name.strip(), "成绩": ""}
    
    return {}  # 无法匹配时返回空字典

学生数据组合

def dfAnswer(folder_path='.'):
    """
    从指定文件夹读取文件,提取学号、姓名信息,生成完整的成绩DataFrame
    
    功能特点:
    1. 按顺序生成1到原DataFrame最大学号值的完整学号序列
    2. 没有对应姓名的学号显示空字符
    3. 在成绩处标注"未交作业"
    
    参数:
    folder_path: str - 文件夹路径,默认为当前目录
    
    返回:
    pd.DataFrame - 包含完整学号序列、姓名和成绩的DataFrame
    """
    data = []
    file_list = readFilesName(folder_path, ['doc', 'docx', 'xlsx', 'xls'])
    
    for f in file_list:
        if ans := idNameInfo(f):
            data.append(ans)
    
    # 创建原始DataFrame
    df = pd.DataFrame(data)
    
    # 如果没有数据,返回空DataFrame
    if df.empty:
        return pd.DataFrame(columns=['学号', '姓名', '成绩']).set_index(['学号', '姓名', '成绩'])
    
    # 处理学号列,确保都是整数类型(排除包含下划线的学号)
    df['学号_数值'] = pd.to_numeric(df['学号'], errors='coerce')
    
    # 找出最大学号值
    max_id = df['学号_数值'].max()
    
    # 生成1到max_id的完整学号序列
    full_ids = pd.DataFrame({'学号': range(1, int(max_id) + 1)})
    
    # 合并数据
    result_df = pd.merge(full_ids, df[['学号', '姓名', '成绩']], on='学号', how='left')
    
    # 填充缺失值
    result_df['姓名'] = result_df['姓名'].fillna('')
    result_df['成绩'] = result_df['成绩'].fillna('未按要求交作业')
    
    # 重新排序并设置索引
    result_df = result_df.sort_values('学号').set_index(['学号', '姓名', '成绩'])
    
    return result_df

生成excel

df = dfAnswer('.')
with pd.ExcelWriter(f'{Path.cwd().name}.xlsx') as writer:
    df.to_excel(writer, sheet_name="学生作业")