学生上交作业并统计
项目介绍
一款基于Python语言开发的学生作业上交统计工具,专为内网环境下学生上机操作,无纸化交作业,收集后自动统计。
解决日常作业,N多学生,不知道谁没交,日常统计平时分记录。
背景与价值
痛点
- 学生在上交作业时,总有学生不按你 的要求来做,如要求文档的名称是“学号+姓名”,那你收到 情况可能如下:
- 中间加空格“1 王小二“
- 学号放后面 “王小二 1”
- 加其它文号 “2号 王小二”
- 收集文档时 应用会增加一些前缀,如“$admin_”
- 统计学生的日常分时,需要一个个的核对,很不方便。
目标
- 检查按上交的目录,生成对应的学生清单
- 默认学号是连续的。对学号 不连续 的,进行补位,并标注“未按要求上交作业”
- 生成EXCEL文档,以方便后续日常分 的统计。
适用场景
- 不方便使用在线作业系统,需要现场收集DOC或XLS的场景
核心功能
- 通过读取当前文件夹下的所有doc/docx/xls/xlsx文件,排除文件夹。
- 通过读取文件名称,通过用RE正则表达式,过滤不符合的文件名。
- 清理后的文件名,按规则生成学号 和 姓名。
- 按最大学号,补齐中间缺失学号,并标记“未按要求上交作业”
- 按文件夹名称生成EXCEL,方便汇总。
技术特点
- 无中间件依赖(打包好的运行文件)
- 轻量化设计(脚本用jupyter-notebook,可分段测试)
快速开始
安装:使用UV安装 同步现有依赖 uv sync
文件名要以学号+姓名来修改,用来获取。
结果展示
原始文档
引用包代码
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="学生作业")