【办公类-119-02】20260201三个园区“国旗下讲话” 按班级组合docx模板(AI+excel+python)


背景需求
上学期国旗下讲话,按照园区合并成一个WORD(里面有22张表格),并发布成共享编辑模式

但是实际操作中,出现问题
1、网络数据遗失:
10月我在网络共同编辑里面写好内容,插入照片,但是1月再看,内容不见了,只能重新做
2、填写不便:
部分老师说:不会用共享编辑,搭档就单独发了WORD合并版本,但是里面有22个表格,班主任需要翻页找到自己班级的页面,填写内容,再翻页4页或6页,才能找到自己的内容。很多老师只能删除非自己班级的页面内容,再填写3、
3、照片移位:
一个格子插入两张照片,照片的插入样式多样(嵌入型,上下环绕、四周环绕),照片大量移位。打印时需要检查和调整位置,确保表格内容在一页上。
4、打印繁琐:
有些老师在共享表格填写,需要下载后,打开WORD,挑选有内容的页面打印。有些老师是填写WORD,也要挑选有内容的页面打印部分。
5、文本字体格式不同
托小班表格只有一页内容,发言数量(幼儿20字,教师100-200字)正好,所以字体、大小、间距与预设一样
横版或竖版照片都能缩小插入,表格不会到第二行

但是中大班是两页内容,发言文字数量有多有少,班主任就更改了字体大小和间距,导致文本看上去不统一。



6、手动整理
本来做成合并版,是为了打印方面,但是基于上述原因,所以搭档收集了很多在线文档链接、多个单独的word,反而经历了多次的挑选打印。



(一)调整思路1:
我觉得不能用22周合并的共享表格word,而是直接为每个班级提供单独的word
(1)word内部只有属于这个班的周次表格,如小2班只有03周、07周、11周、15周、19周的五张表
(2)把word单独发给班主任,回收也是word文件,打印后,搭档手动排序整理。

(二)调整思路2
为了能让文字的字体、大小、间距等格式一样,在原有的基础信息批量写入(日期、周次、指导老师姓名、班级、幼儿姓名5个、幼儿发言里面的幼儿姓名、园长、主题)的代码基础上,我想把内部的信息全部填满(幼儿发言部分写5条发言,园长发言1条)教师只需要插入两张照片。
这一点可以通过AI生文来实现批量生成统一的字数,Python批量导入

不过搭档说,幼儿发言内容和老师发言内容还是空着,全部填满了,班主任们就更“懒”了,她愿意学期结束时调整文字格式。
我觉得有道理,但是我还是想做一套有全部对话的(没有照片)的模版,来实现“国旗下讲话的文本资料”批量生成的可能

一、回顾
上学期的国旗下,我就用豆包做文本资料。
豆包关键词:我需要写一份小班发言的国旗下讲话,主题是“迎接2026年”,一共五位孩子发言,每人30字。再写一份老师讲话内容月200字。表格形式呈现,只要讲话内容。不要小朋友和老师的字样。不要序号和类别,只要内容

我是AI上生成的文字一条条复制到空白位置上。贴起来很烦,还要选择默认原始格式。所以我迫切希望用AI+Python,把文字自动贴入
二、现在修改


把表格切分一下





sw1-sw5、tw 写入幼儿对话(20字)和教师讲话(100-200字)


以下是总园的模板



后来发现,幼儿五条发言如果用透明线分割,会不好看,有点空。看上去间距大。

间距有点大,所以不需要表格框了

我合并了发言部分的所有表格,姓名+发言都是在一起的


AI批量做幼儿对话5列、教师1列
关键词:
我需要写一份小班发言的国旗下讲话,主题是“XXXX”,一共五位孩子发言每人30字童趣化不要统一的字数、格式。再写一份老师讲话内容200字。表格形式呈现,只要讲话内容。不要小朋友和老师的字样。“XXXX”是下面的主题
校园安全
普通话推广周
校园新发现
喜迎国庆节
运动安全
爱眼护眼
保护地球
我爱运动
消防安全
珍惜粮食不浪费
预防传染病
劳动最光荣
交通安全
我会刷牙
爱护小动物
我爱老师和同伴
迎接2026年
防拐防骗
食品安全
快乐的寒假
(每个主题5位孩子说的话是5列展示,每列30字,第六列是教师发言250字,字不能太少)

deep seek可以生成表格列式样的。

复制到excle内

(一)提取安排表docx+提取excle名单(5个人一周)


抽取每班人员,5人一组(跳过空),到最后了再回到第一个人继续循环5人

代码展示
(1)提取“安排表docx”里面的五个信息

1)周次列:内容提取后,转为阿拉伯数字,第1周,第2周
2)日期列:内容提取后,正则提取第一个数字变成“年.月.日”2026.9.28
3)行政与班级拆开成两列。
(2)提取“班级信息表”的五位幼儿姓名

之前我考虑托班4人一周,小中大班5人一周,但是会出现托班第5人空,但是(顿号、)和(冒号:)还在。

所以这次都是每周5人,循环滚动

''' 读取国旗下讲话安排表docx文件中表格的内容周次、日期、班级、园长、班级、主题、并填充每周五个学生姓名 1. 跳过第一行表头,从第二行开始读取 2. 拆分第4列"行政+班级"为teacher和classroom两列 3. day列统一填充为5 4. 第一列中文数字转阿拉伯数字 5. 第二列日期格式转换 6. 从班级信息表中提取学生姓名并填充到s1-s5列 7. teachers列保持为空 ''' import os import re import pandas as pd from docx import Document from openpyxl import Workbook from openpyxl.styles import Alignment def docx_table_to_excel_with_names(docx_path, excel_path, class_info_path): """ 读取docx文件中表格的内容并填充学生姓名 1. 跳过第一行表头,从第二行开始读取 2. 拆分第4列"行政+班级"为teacher和classroom两列 3. day列统一填充为5 4. 第一列中文数字转阿拉伯数字 5. 第二列日期格式转换 6. 从班级信息表中提取学生姓名并填充到s1-s5列 7. teachers列保持为空 :param docx_path: docx文件路径 :param excel_path: 输出xlsx路径 :param class_info_path: 班级信息表路径 """ # 中文数字到阿拉伯数字的映射 num_map = { "二十一": "21", "十一": "11", "十二": "12", "十三": "13", "十四": "14", "十五": "15", "十六": "16", "十七": "17", "十八": "18", "十九": "19", "二十": "20", "一": "1", "二": "2", "三": "3", "四": "4", "五": "5", "六": "6", "七": "7", "八": "8", "九": "9", "十": "10", } # 正则表达式:匹配 数字+月/数字+日 或 数字/数字 的首个日期片段 date_pattern = re.compile(r'(\d{1,2})[月/](\d{1,2})') # 1. 读取docx中的表格 try: doc = Document(docx_path) # 获取第一个表格(假设只有一个目标表格) table = doc.tables[0] if not table: print("警告:docx文件中未找到表格") return print(f"表格行数: {len(table.rows)}") except FileNotFoundError: print(f"错误:未找到文件 {docx_path},请检查路径是否正确") return except Exception as e: print(f"读取docx文件失败:{str(e)}") return # 2. 读取班级信息表 class_students = {} if class_info_path and os.path.exists(class_info_path): try: class_info = pd.read_excel(class_info_path, sheet_name=None) # 读取所有工作表 print(f"成功读取班级信息表:{class_info_path}") # 为每个班级创建学生姓名列表(按学号排序) for sheet_name, df in class_info.items(): if sheet_name not in ['模板'] and '班' in sheet_name: # 跳过模板表,只处理班级表 # 确保列名正确 if '姓名' in df.columns and '学号' in df.columns: # 按学号排序并过滤掉空姓名 students = df[df['姓名'].notna()].sort_values('学号')['姓名'].tolist() # 清理班级名称(去掉括号) clean_class_name = sheet_name.replace('(', '').replace(')', '').strip() class_students[clean_class_name] = students print(f"班级 {clean_class_name}: {len(students)} 名学生") else: print(f"警告:工作表 {sheet_name} 缺少'姓名'或'学号'列") except Exception as e: print(f"读取班级信息表失败:{str(e)}") else: print(f"警告:班级信息表不存在,将仅提取表格数据而不填充学生姓名") # 为每个班级创建索引跟踪器 class_index_tracker = {class_name: 0 for class_name in class_students.keys()} # 3. 创建并配置xlsx文件 wb = Workbook() ws = wb.active ws.title = "国旗下讲话安排" # 4. 写入指定表头 headers = ["week", "date", "topic", "leader", "classroom", "day", "teachers", "s1", "s2", "s3", "s4", "s5", "sw1", "sw2", "sw3", "sw4", "sw5", "tw"] ws.append(headers) # 5. 读取表格第二行及以后的数据并处理 processed_rows = [] for row_idx, row in enumerate(table.rows): # 跳过第一行表头 if row_idx == 0: continue # 提取每行单元格的文本 row_data = [cell.text.strip() for cell in row.cells] # 确保有足够的数据列 while len(row_data) < 4: row_data.append("") # 处理第一列:中文数字转阿拉伯数字 week_text = row_data[0] for cn_num, num in num_map.items(): if cn_num in week_text: week_text = week_text.replace(cn_num, num) break # 处理第二列:日期格式转换 raw_date = row_data[1] match = date_pattern.search(raw_date) if match: month = match.group(1) day = match.group(2) formatted_date = f"2026.{month}.{day}" else: formatted_date = raw_date # 无匹配则保留原数据 # 处理其他列数据 topic = row_data[2] if len(row_data) > 2 else "" admin_class = row_data[3] if len(row_data) > 3 else "" # 按"+"拆分行政和班级 split_list = admin_class.split("+") teacher = split_list[0].strip() if len(split_list) > 0 else "" classroom_raw = split_list[1].strip() if len(split_list) > 1 else "" # 清理班级名称(去掉括号) classroom = classroom_raw.replace('(', '').replace(')', '').strip() # 提取学生姓名 student_names = [] if classroom in class_students and class_students[classroom]: students = class_students[classroom] current_index = class_index_tracker.get(classroom, 0) # 根据班级类型确定每组人数 if '托' in classroom: # 托班:4人一组 group_size = 5 elif '小' in classroom: # 小班:5人一组 group_size = 5 else: # 其他班级默认5人一组 group_size = 5 # 获取当前组的学生 for i in range(group_size): if current_index < len(students): student_names.append(students[current_index]) current_index += 1 else: # 如果到末尾,从头开始 current_index = 0 if len(students) > 0: # 确保班级有学生 student_names.append(students[current_index]) current_index += 1 else: student_names.append("") # 更新索引跟踪器 class_index_tracker[classroom] = current_index else: # 如果没有找到班级或班级没有学生,填充空值 student_names = [""] * 5 # 填充s1-s5 s1 = student_names[0] if len(student_names) > 0 else "" s2 = student_names[1] if len(student_names) > 1 else "" s3 = student_names[2] if len(student_names) > 2 else "" s4 = student_names[3] if len(student_names) > 3 else "" s5 = student_names[4] if len(student_names) > 4 else "" # 组装最终行数据 - teachers列保持为空字符串 final_row = [ week_text, # week formatted_date, # date topic, # topic teacher, # leader (行政老师) classroom, # classroom 5, # day "", # teachers (保持为空) s1, s2, s3, s4, s5, # s1-s5 "", "", "", "", "", # sw1-sw5 "" # tw ] processed_rows.append(final_row) # 打印进度 print(f"处理第{row_idx}行: 行政老师='{teacher}', 班级='{classroom}' -> 学生: {student_names[:5]}") # 6. 将所有处理后的行写入工作表 for row_data in processed_rows: ws.append(row_data) # 7. 设置列宽 column_widths = [10, 14, 14, 10, 10, 10, 20, 10, 10, 10, 10, 10, 30, 30, 30, 30, 30, 100] print(f"列数: {len(column_widths)}") for col_idx, width in enumerate(column_widths, 1): # 列索引从1开始 column_letter = chr(64 + col_idx) if col_idx <= 26 else chr(64 + (col_idx-1)//26) + chr(64 + col_idx%26) ws.column_dimensions[column_letter].width = width # 8. 设置所有单元格水平、垂直居中对齐 alignment = Alignment(horizontal='center', vertical='center') for row in ws.iter_rows(): for cell in row: cell.alignment = alignment # 9. 保存xlsx文件 try: wb.save(excel_path) print(f"成功写入文件:{excel_path}") print(f"共写入 {len(processed_rows)} 行数据(不含表头)") if class_students: # 打印每个班级的最后索引位置 print("\n各班级学生分配索引位置:") for class_name, index in class_index_tracker.items(): total_students = len(class_students.get(class_name, [])) print(f" {class_name}: 索引 {index}/{total_students}") except Exception as e: print(f"保存xlsx文件失败:{str(e)}") # 主程序执行 if __name__ == "__main__": path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20260301国旗下讲话\01一分园' DOCX_FILE = path + r"\00 参考国旗下的讲话总安排(一分部) 2026.2.docx" EXCEL_FILE = path + r"\01 EXCLE(一分部) 2026.2_已填充.xlsx" CLASS_INFO_FILE = path + r"\班级信息表.xlsx" # 班级信息表路径 docx_table_to_excel_with_names(DOCX_FILE, EXCEL_FILE, CLASS_INFO_FILE)提取WORD表格内第一张工作表

生成了excle,填充了左侧docx的内容和右侧的幼儿5人一组



手动把班主任姓名、幼儿发言5列,园长发言1列都贴入

(2)写入模板内,按照班级合并一个docx, 把22周合并一个docx




''' 一分园,国旗下讲话 把EXCLE模版内容写入WORD 豆包、Deepseek、阿夏 202602023 ''' import pandas as pd from docxtpl import DocxTemplate import os from docx import Document import numpy as np import win32com.client as win32 # ========== 核心优化:路径处理 ========== # 根路径(根据你的文件位置调整,建议用绝对路径) root_path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20260301国旗下讲话\01一分园' # 生成文档的临时文件夹 temp_folder = os.path.join(root_path, "202602一分园国旗下讲话模版") os.makedirs(temp_folder, exist_ok=True) # 存储生成的文件路径 generated_files = [] # ========== 1. 读取班级信息表和安排表 ========== # 班级信息表路径 class_info_path = os.path.join(root_path, "班级信息表.xlsx") # 国旗下讲话安排表路径 schedule_path = os.path.join(root_path, "02EXCLE(一分部) 2026.2_已填充.xlsx") # 模板文件路径 template_path = os.path.join(root_path, "02模板一分园(小 1 班)国旗下的讲话(第 2 周).docx") # 检查关键文件是否存在 if not os.path.exists(class_info_path): print(f"错误:班级信息表不存在 - {class_info_path}") exit() if not os.path.exists(schedule_path): print(f"错误:安排表不存在 - {schedule_path}") exit() if not os.path.exists(template_path): print(f"错误:模板文件不存在 - {template_path}") exit() # 读取班级信息表(所有工作表) class_info = pd.read_excel(class_info_path, sheet_name=None) # 读取安排表 schedule = pd.read_excel(schedule_path) # 确保s1-s5列是字符串类型,避免nan显示 for col in ['s1', 's2', 's3', 's4', 's5']: if col in schedule.columns: schedule[col] = schedule[col].astype(str).replace('nan', '') # ========== 2. 按班级+学号排序生成学生名单 ========== class_students = {} # 班级: [学生姓名列表] for sheet_name, df in class_info.items(): # 只处理含"班"的工作表,跳过模板表 if '班' in sheet_name and sheet_name not in ['模板']: # 过滤空姓名 + 按学号升序排序 valid_df = df[df['姓名'].notna() & (df['姓名'] != '')].sort_values(by='学号', ascending=True) students = valid_df['姓名'].tolist() class_students[sheet_name] = students print(f"✅ 班级 {sheet_name}: 共{len(students)}名学生") # 班级索引跟踪器(记录每个班级下一组该取第几个学生) class_index_tracker = {cls: 0 for cls in class_students.keys()} # ========== 3. 填充安排表的s1-s5列 ========== for index, row in schedule.iterrows(): classroom = str(row['classroom']).strip() # 兼容班级名称格式(比如"小(1)班"转"小1班") clean_classroom = classroom.replace('(', '').replace(')', '').replace('(', '').replace(')', '') if clean_classroom in class_students: students = class_students[clean_classroom] current_idx = class_index_tracker[clean_classroom] total_students = len(students) # 确定每组人数:托班4人,小班5人 group_size = 4 if '托' in clean_classroom else 5 # 选取当前组学生(循环取,用完从头再来) selected = [] for i in range(group_size): selected.append(students[current_idx % total_students]) current_idx += 1 # 更新索引 class_index_tracker[clean_classroom] = current_idx # 填充到s1-s5列(不足5人的补空) for i in range(5): col_name = f's{i+1}' schedule.at[index, col_name] = selected[i] if i < len(selected) else "" print(f"📌 第{index+1}行 {clean_classroom}: {selected}") else: print(f"⚠️ 未找到班级 {clean_classroom} 的学生信息") # 保存填充后的安排表(覆盖原文件) schedule.to_excel(schedule_path, index=False) print(f"\n✅ 已保存填充后的安排表: {schedule_path}") # ========== 4. 批量生成各班国旗下讲话文档 ========== try: # 验证模板文件是否有效 test_doc = Document(template_path) print("✅ 模板文件验证通过,开始生成文档...") except Exception as e: print(f"❌ 模板文件无效: {e}") exit() for index, row in schedule.iterrows(): try: # 每次重新加载模板,避免数据污染 tpl = DocxTemplate(template_path) # 格式化班级名称(小1班 → 小(1)班) classroom_str = str(row['classroom']) if len(classroom_str) >= 3 and classroom_str[1].isdigit(): formatted_class = f"{classroom_str[0]}({classroom_str[1]}){classroom_str[2:]}" else: formatted_class = classroom_str # 构建模板上下文(包含所有占位符) context = { "date": str(row['date']), "week": str(row['week']), "teachers": str(row['teachers']) if pd.notna(row['teachers']) else "待定", "classroom": formatted_class, "topic": str(row['topic']), "leader": str(row['leader']), # 幼儿姓名 "s1": str(row['s1']) if pd.notna(row['s1']) and row['s1'] != 'nan' else "", "s2": str(row['s2']) if pd.notna(row['s2']) and row['s2'] != 'nan' else "", "s3": str(row['s3']) if pd.notna(row['s3']) and row['s3'] != 'nan' else "", "s4": str(row['s4']) if pd.notna(row['s4']) and row['s4'] != 'nan' else "", "s5": str(row['s5']) if pd.notna(row['s5']) and row['s5'] != 'nan' else "", # 幼儿讲话内容占位符(提示填写) "sw1": str(row['sw1']), "sw2": str(row['sw2']), "sw3": str(row['sw3']), "sw4": str(row['sw4']), "sw5": str(row['sw5']), # 老师讲话内容占位符 "tw": str(row['tw']), } # 渲染模板 tpl.render(context) # 格式化周次(第2周 → 第02周,方便文件排序) week_str = str(row['week']).replace('第', '').replace('周', '') try: week_formatted = f"第{int(week_str):02d}周" except: week_formatted = str(row['week']) # 生成文件名 filename = f"{week_formatted}_一分园_{row['classroom']}_国旗下的讲话_({row['topic']}).docx" file_full_path = os.path.join(temp_folder, filename) # 保存文档 tpl.save(file_full_path) generated_files.append(file_full_path) print(f"✅ 生成文档: {filename}") except Exception as e: print(f"❌ 生成第{index+1}行文档失败: {str(e)}") continue print(f"\n📊 文档生成完成!共生成 {len(generated_files)} 个文档") # ========== 5. 合并所有生成的文档 ========== def merge_word_documents(files, output_path): """合并多个Word文档,清理空行,只保留分页符""" if not files: print("⚠️ 没有文档可合并") return try: # 启动Word应用 word = win32.gencache.EnsureDispatch('Word.Application') word.Visible = False word.DisplayAlerts = False # 打开第一个文档作为基础 merged_doc = word.Documents.Open(files[0]) selection = word.Selection # 移动到文档末尾并清理空行 selection.EndKey(Unit=6) # wdStory = 6 clean_extra_empty_lines(selection) # 合并其他文档 for i, file_path in enumerate(files[1:], 1): if not os.path.exists(file_path): print(f"⚠️ 文件不存在,跳过: {os.path.basename(file_path)}") continue print(f"🔗 合并第{i+1}个文档: {os.path.basename(file_path)}") # 插入分页符 selection.InsertBreak(7) # wdPageBreak = 7 # 插入文档内容 selection.InsertFile( FileName=file_path,, ConfirmConversions=False, Link=False, Attachment=False ) # 清理空行 selection.EndKey(Unit=6) clean_extra_empty_lines(selection) # 保存合并后的文档 merged_doc.SaveAs(output_path) merged_doc.Close() print(f"\n✅ 合并完成!保存路径: {output_path}") except Exception as e: print(f"❌ 合并文档失败: {str(e)}") import traceback traceback.print_exc() finally: # 关闭Word try: word.Quit(SaveChanges=0) except: pass def clean_extra_empty_lines(selection): """清理光标前的空行(表格后的回车符)""" for _ in range(10): # 最多检查10层,避免无限循环 if selection.MoveLeft(Unit=1, Count=1): # wdCharacter = 1 if selection.Paragraphs(1).Range.Text.strip() == "": selection.Paragraphs(1).Range.Delete() else: break else: