1. 项目背景与需求解析
开学季对于幼儿园行政老师来说总是异常忙碌,特别是需要为每个班级准备大量基础信息材料。去年我负责幼儿园开学资料准备时,光是打印、分类、装袋就花了整整三天时间,还经常出现班级材料混淆的情况。今年我决定用Python写个小工具,把点名册、周计划表、挂牌、圆牌等材料按班级自动合并打包,直接生成压缩包发给对应班主任。
这个工具主要解决三个痛点:
- 避免人工操作导致的班级材料错配
- 大幅减少重复性文件整理时间
- 确保每个班主任只收到自己班级的完整资料包
2. 技术方案设计
2.1 文件组织结构设计
首先需要规范原始文件的存放结构。我在服务器上建立了如下目录:
/开学资料/ ├── 原始文件/ │ ├── 点名册/ │ │ ├── 小一班.xlsx │ │ ├── 小二班.xlsx │ │ └── ... │ ├── 周计划表/ │ │ ├── 小一班.docx │ │ ├── 小二班.docx │ │ └── ... │ ├── 挂牌模板.pptx │ ├── 圆牌模板.pptx │ └── 幼儿信息.csv └── 输出结果/2.2 关键技术实现
2.2.1 班级信息提取
从园园通系统导出的幼儿信息CSV包含所有班级数据,需要先按班级拆分:
import pandas as pd def split_by_class(csv_path): df = pd.read_csv(csv_path) class_groups = df.groupby('班级') class_data = {} for class_name, group in class_groups: class_data[class_name] = group return class_data2.2.2 模板文件处理
挂牌和圆牌使用统一的PPT模板,需要根据班级信息动态生成:
from pptx import Presentation def generate_name_tag(template_path, class_name, output_path): prs = Presentation(template_path) # 第一页是挂牌 slide = prs.slides[0] for shape in slide.shapes: if shape.has_text_frame and "班级名称" in shape.text: shape.text = shape.text.replace("班级名称", class_name) # 第二页是圆牌 slide = prs.slides[1] # ...类似处理... prs.save(output_path)2.2.3 文件打包分发
使用zipfile模块创建按班级分类的压缩包:
import zipfile import os def create_class_package(class_name, files_dict, output_dir): zip_path = os.path.join(output_dir, f"{class_name}_开学资料包.zip") with zipfile.ZipFile(zip_path, 'w') as zipf: for file_type, file_path in files_dict.items(): arcname = f"{class_name}_{file_type}{os.path.splitext(file_path)[1]}" zipf.write(file_path, arcname) return zip_path3. 完整实现流程
3.1 准备工作
- 安装依赖库:
pip install pandas python-pptx openpyxl- 准备模板文件:
- 挂牌模板.pptx:包含可替换的占位文本
- 圆牌模板.pptx:同上
- 周计划表模板.docx:各班级通用模板
3.2 主程序逻辑
import shutil from pathlib import Path def main(): base_dir = Path("/开学资料") raw_dir = base_dir / "原始文件" output_dir = base_dir / "输出结果" # 清空输出目录 shutil.rmtree(output_dir, ignore_errors=True) output_dir.mkdir(exist_ok=True) # 按班级处理幼儿信息 class_data = split_by_class(raw_dir / "幼儿信息.csv") for class_name, students_df in class_data.items(): # 生成班级专属文件 class_files = {} # 处理点名册 roster_path = raw_dir / "点名册" / f"{class_name}.xlsx" class_files["点名册"] = roster_path # 处理周计划表 plan_path = raw_dir / "周计划表" / f"{class_name}.docx" class_files["周计划表"] = plan_path # 生成挂牌和圆牌 name_tag_path = output_dir / f"{class_name}_挂牌.pptx" generate_name_tag(raw_dir / "挂牌模板.pptx", class_name, name_tag_path) class_files["挂牌"] = name_tag_path # 打包班级资料 create_class_package(class_name, class_files, output_dir) if __name__ == "__main__": main()4. 实际应用中的优化点
4.1 性能优化
当处理大规模幼儿园(如超过20个班级)时,可以做以下优化:
- 使用多线程处理:
from concurrent.futures import ThreadPoolExecutor def process_class(class_name, students_df): # 封装单个班级的处理逻辑 ... with ThreadPoolExecutor(max_workers=4) as executor: futures = [] for class_name, students_df in class_data.items(): futures.append(executor.submit(process_class, class_name, students_df)) for future in futures: future.result()4.2 错误处理
增加健壮性检查:
def safe_generate_name_tag(template_path, class_name, output_path): try: if not os.path.exists(template_path): raise FileNotFoundError(f"模板文件不存在: {template_path}") generate_name_tag(template_path, class_name, output_path) return True except Exception as e: print(f"生成{class_name}挂牌失败: {str(e)}") return False4.3 日志记录
添加详细日志方便排查问题:
import logging logging.basicConfig( filename='开学资料处理.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) logging.info(f"开始处理班级: {class_name}") try: # 处理逻辑 logging.info(f"成功处理班级: {class_name}") except Exception as e: logging.error(f"处理班级{class_name}出错: {str(e)}")5. 实际部署注意事项
文件权限问题:
- 确保程序对原始文件和输出目录有读写权限
- 在Linux服务器上运行时注意用户组权限
路径兼容性:
- 使用Path对象代替字符串路径(如上面的示例)
- 处理Windows和Linux路径分隔符差异
内存管理:
- 处理大型Excel文件时使用chunksize参数分块读取
- 及时关闭文件句柄
版本控制:
- 模板文件变更时更新版本号
- 在压缩包名称中包含生成日期
6. 扩展功能
6.1 邮件自动发送
集成邮件发送功能,直接发送给班主任:
import smtplib from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email import encoders def send_to_teacher(email, attachment_path): msg = MIMEMultipart() msg['Subject'] = f"{os.path.basename(attachment_path)} - 开学资料" msg['From'] = "admin@kindergarten.com" msg['To'] = email part = MIMEBase('application', "octet-stream") with open(attachment_path, 'rb') as file: part.set_payload(file.read()) encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="{}"'.format(os.path.basename(attachment_path))) msg.attach(part) with smtplib.SMTP('smtp.server.com') as server: server.send_message(msg)6.2 生成进度报告
在流程结束后生成处理报告:
def generate_report(output_dir, class_count, error_list): report_path = output_dir / "处理报告.txt" with open(report_path, 'w') as f: f.write(f"开学资料处理报告\n") f.write(f"生成时间: {datetime.now()}\n") f.write(f"成功处理班级数: {class_count - len(error_list)}\n") f.write(f"失败班级数: {len(error_list)}\n") if error_list: f.write("\n失败详情:\n") for class_name, error in error_list: f.write(f"{class_name}: {error}\n")7. 常见问题解决
中文乱码问题:
- 在文件开头添加编码声明:
# -*- coding: utf-8 -*- - 读取CSV时指定编码:
pd.read_csv(..., encoding='utf-8-sig')
- 在文件开头添加编码声明:
文件被占用无法删除:
- 确保所有文件句柄都已关闭
- 使用
try-finally块保证资源释放
模板文件被意外修改:
- 处理前先复制模板到临时目录
- 使用版本控制管理模板文件
内存不足处理大文件:
- 使用
pd.read_csv(..., chunksize=1000)分块读取 - 考虑使用Dask等库处理超大数据
- 使用
这个工具在我们幼儿园实际使用后,原本需要3天的手工工作现在20分钟就能完成,而且完全避免了人为错误。班主任们反馈收到的资料包完整规范,再也不会出现漏发错发的情况了。