背景需求4月份转了两位插班生进来之前做了一份word内容提取到excel的代码【办公类-142-03】20260304插班生word转长表EXCLE3从word表格按行导出列表提取索引内容。写入EXCLE长表另存有名字的文件名https://mp.csdn.net/mp_blog/creation/editor/158653780所以我需要word版本而不是手写版本需要我来重新手打成电子稿家长填了手写的报名表我请班主任填写word电子稿本次用了新的长表excel制作过程从班主任那里获得word电子版下载新的长表xlsx和填报操作要求重新开一个文件夹打开新的excel标题栏1、增加了“变更类型”“班级”2、增加三行范例斜线灰色底纹便于参考3、所有的文字的格式、样式、插入都不能修改已经限定格式了减少输入时的格式问题找到上一次的代码复制一份把两个代码合并原来代码写入excel的字母列修改代码写入excel的字母列也有所变化重新复制一个excel模版名称为“幼儿导入模版-XXXX幼儿园”把我园的代码先贴在第6-8行本次只用一个excel模版更方便原来用两个模版小班、非小班 插班生园园通word转新版长表EXCLE0604读取每行去重获取指定行指定索引内容 元素内部空格去掉多人读取每行去重提取索引内部空格删除复制模版添加名字写入指定的格子内 有三行参考内容虚线底纹 deepseek、阿夏 20260330 import os import re import warnings import datetime import pythoncom import win32com.client as win32 from docx import Document warnings.filterwarnings(ignore) # 屏蔽无关警告 # 第一部分DOC转DOCX功能 def doc_to_docx(doc_path, docx_path): 将.doc文件转换为.docx文件需Office Word环境Windows专属 try: word win32.Dispatch(Word.Application) word.Visible False # 后台运行不显示Word窗口 word.DisplayAlerts 0 # 屏蔽弹窗提示 doc word.Documents.Open(doc_path) # 另存为docx格式12对应docx格式 doc.SaveAs2(docx_path, FileFormat12) doc.Close() word.Quit() print(f 成功转换{os.path.basename(doc_path)} - {os.path.basename(docx_path)}) return True except Exception as e: print(f❌ 转换失败{os.path.basename(doc_path)}错误{str(e)[:80]}) return False def batch_convert_doc_to_docx(word_folder): 批量将指定文件夹中的.doc文件转换为.docx文件 # 校验文件夹是否存在 if not os.path.exists(word_folder): print(f❌ 错误文件夹 {word_folder} 不存在请检查路径) return 0 # 筛选出所有.doc文件排除.docx doc_files [f for f in os.listdir(word_folder) if f.lower().endswith(.doc) and not f.lower().endswith(.docx)] if not doc_files: print(f 文件夹 {word_folder} 中未找到任何.doc文件无需转换) return 0 print(f 共找到 {len(doc_files)} 个.doc文件开始转换为.docx...\n) # 批量转换 success_count 0 for doc_file in doc_files: doc_path os.path.join(word_folder, doc_file) docx_file os.path.splitext(doc_file)[0] .docx docx_path os.path.join(word_folder, docx_file) # 避免重复转换已存在的docx文件 if os.path.exists(docx_path): print(fℹ️ 跳过{docx_file} 已存在无需重复转换) continue if doc_to_docx(doc_path, docx_path): success_count 1 # 转换完成统计 print(f\n 转换完成总计{len(doc_files)} 个.doc文件成功转换 {success_count} 个失败 {len(doc_files)-success_count} 个) return success_count # 第二部分DOCX提取功能 def clean_cell_text(text): 清洗单元格文字移除br/、多余空格、换行符处理空值 if not text: return 空单元格 # 移除br/、HTML标签 clean_text re.sub(rbr\s*/?, , text) # 移除所有空格包括中间的空格 clean_text re.sub(r\s, , clean_text) clean_text clean_text.strip() return clean_text if clean_text else 空单元格 def remove_duplicates_from_row(row_cells): 去除行中的重复内容同时保持顺序 seen set() unique_cells [] for cell in row_cells: if cell not in seen: seen.add(cell) unique_cells.append(cell) return unique_cells def extract_last_18_digits(text): 提取字符串中的最后18位数字包括X结尾的情况 if not text or text 空单元格 or text 列索引超出范围: return text pattern r\d[XX]? matches re.findall(pattern, text) if matches: last_match matches[-1] if len(last_match) 18: return last_match[-18:] elif len(last_match) 18: return last_match else: all_digits re.findall(r\d, text) if len(all_digits) 18: return .join(all_digits[-18:]) return text def convert_date_format(date_str): 将中文日期格式转换为斜杠格式 if not date_str or date_str 空单元格 or date_str 列索引超出范围: return date_str # 匹配 2025年3月30日 格式 pattern1 r(\d{4})年(\d{1,2})月(\d{1,2})日 match re.search(pattern1, date_str) if match: year, month, day match.groups() return f{year}/{month}/{day} # 匹配 2025-03-30 或 2025/03/30 格式 pattern2 r(\d{4})[-/](\d{1,2})[-/](\d{1,2}) match re.search(pattern2, date_str) if match: year, month, day match.groups() return f{year}/{month}/{day} # 匹配 2025.3.30 格式 pattern3 r(\d{4})\.(\d{1,2})\.(\d{1,2}) match re.search(pattern3, date_str) if match: year, month, day match.groups() return f{year}/{month}/{day} # 如果已经是其他格式保持不变 return date_str def extract_single_docx_info(docx_path, extract_config, print_rowsTrue): 从单个DOCX文件中提取指定信息 :param docx_path: DOCX文件路径 :param extract_config: 提取配置列表每个元素为 (row_index, col_index, field_name) row_index: 行索引0-based col_index: 该行列表的列索引0-based field_name: 字段名 result {} try: doc Document(docx_path) file_name os.path.basename(docx_path) print(f\n{*60}) print(f 文件{file_name}) print(f{*60}) for table_idx, table in enumerate(doc.tables, start1): print(f\n 表格 {table_idx}:) print(f{-*40}) # 先打印所有行的信息帮助调试 print( 表格所有行内容已清洗空格) for row_idx, row in enumerate(table.rows): row_cells [clean_cell_text(cell.text) for cell in row.cells] unique_row remove_duplicates_from_row(row_cells) print(f 行 {row_idx} (列数:{len(unique_row)}): {unique_row}) print(f\n{*10} 开始提取数据 {*10}) # 按配置提取数据 for config in extract_config: target_row, target_col, field_name config # 检查目标行是否存在 if target_row len(table.rows): row table.rows[target_row] row_cells [clean_cell_text(cell.text) for cell in row.cells] unique_row remove_duplicates_from_row(row_cells) # 检查列索引是否在范围内 if target_col len(unique_row): value unique_row[target_col] # 特殊处理身份证号字段 if field_name in [ID, IDparent]: original_value value value extract_last_18_digits(value) if value ! original_value: print(f 身份证号处理: {original_value} - {value}) # 特殊处理生日字段 - 转换日期格式 if field_name birthday: original_value value value convert_date_format(value) if value ! original_value: print(f 日期格式转换: {original_value} - {value}) result[field_name] value else: error_msg f列索引{target_col}超出范围(该行只有{len(unique_row)}列) result[field_name] error_msg else: error_msg f行索引{target_row}超出范围(表格只有{len(table.rows)}行) result[field_name] error_msg # 打印提取结果汇总 if result: print(f\n{✅*10} 提取结果汇总 {✅*10}) for key, value in result.items(): if 超出范围 in str(value): print(f {key}: ❌ {value}) else: print(f {key}: {value}) print(f{✅*30}) return result except Exception as e: print(f❌ 处理文件 {os.path.basename(docx_path)} 失败{str(e)}) return None def write_to_excel_with_win32com(template_path, output_path, data_list, start_row5): 使用win32com将提取的数据写入Excel文件保留所有格式和序列 :param template_path: 模板Excel文件路径 :param output_path: 输出Excel文件路径 :param data_list: 数据列表每个元素是一个字典包含提取的数据 :param start_row: 开始写入的行号从1开始计数 try: pythoncom.CoInitialize() # 初始化COM组件 print(f 正在打开Excel应用程序...) # 创建Excel应用程序对象 excel win32.DispatchEx(Excel.Application) excel.Visible False # 不显示Excel界面 excel.DisplayAlerts False # 不显示警告 # 打开模板文件 print(f 打开模板文件{template_path}) wb excel.Workbooks.Open(os.path.abspath(template_path)) # 获取第一个工作表 ws wb.Worksheets(1) # 自定义默认值 default_grade 中班 # 默认年级 default_comeday 2026-3-3 # 默认来园日期 # 定义列映射字段名 - Excel列字母 column_mapping { name: F, # F列 姓名 sex: H, # H列 性别 Hometown: U, # V列 籍贯 Ethnicity: P, # T列 民族 birthday: I, # I列 出生日期 birthplace: R, # U列 出生地 ID: K, # E列 幼儿身份证号 PPA: AG, # Y列 户口所在地选择省市区 PNC: AJ, # AB列 户口的街道居委 PPA2: AN, # AD列 户口所在地详细地址 RA: AP, # AE列 居住所在地选择省市区 RNC: AS, # AH列 居住地的街道居委 RA2: AW, # AK列 居住地详细地址 parent: BB, # BA列 监护人与幼儿关系 parentname: AX, # AW列 监护人姓名 IDparent: AZ, # AY列 监护人身份证号 phone: BA # AZ列 监护人手机号 } # 定义默认值映射字段名 - Excel列字母 default_mapping { grade: J, # J列 班级年级 comeday: L # L列 来园日期 } print(f 开始写入数据从第{start_row}行开始) # 写入数据 for row_idx, data in enumerate(data_list, startstart_row): print(f\n 正在写入第{row_idx}行数据...) # 写入从Word提取的数据 for field_name, col_letter in column_mapping.items(): value data.get(field_name, ) # 如果值是错误信息不写入 if 超出范围 in str(value) or value 提取失败 or value 未找到: continue if value: cell_address f{col_letter}{row_idx} # 特殊处理生日字段以日期格式写入 if field_name birthday: try: # 尝试将字符串转换为日期 date_parts re.split(r[/\-], value) if len(date_parts) 3: year, month, day map(int, date_parts) date_obj datetime.date(year, month, day) ws.Range(cell_address).Value date_obj ws.Range(cell_address).NumberFormat yyyy/m/d print(f 写入生日日期到 {cell_address}: {value}) else: ws.Range(cell_address).Value value print(f 写入生日文本到 {cell_address}: {value}) except: ws.Range(cell_address).Value value print(f 写入生日文本到 {cell_address}: {value}) else: ws.Range(cell_address).Value value print(f 写入 {field_name} 到 {cell_address}: {value}) # 写入默认来园日期 comeday_address f{default_mapping[comeday]}{row_idx} try: date_parts re.split(r[/\-], default_comeday) if len(date_parts) 3: year, month, day map(int, date_parts) date_obj datetime.date(year, month, day) ws.Range(comeday_address).Value date_obj ws.Range(comeday_address).NumberFormat yyyy/m/d print(f 写入来园日期到 {comeday_address}: {default_comeday}) except: ws.Range(comeday_address).Value default_comeday print(f 写入来园日期文本到 {comeday_address}: {default_comeday}) # 写入年级 grade_address f{default_mapping[grade]}{row_idx} ws.Range(grade_address).Value default_grade print(f 写入年级到 {grade_address}: {default_grade}) # 保存文件 print(f 正在保存文件到{output_path}) wb.SaveAs(os.path.abspath(output_path)) # 关闭工作簿和Excel wb.Close(SaveChangesFalse) excel.Quit() pythoncom.CoUninitialize() # 释放COM组件 print(f✅ 数据已成功写入Excel文件{output_path}) print(f 默认写入年级{default_grade}, 来园日期{default_comeday}) return True except Exception as e: print(f❌ 写入Excel失败{str(e)}) import traceback traceback.print_exc() # 确保Excel被关闭 try: excel.Quit() except: pass pythoncom.CoUninitialize() return False def batch_extract_and_write_to_excel(folder_path, extract_config, template_excel_path, start_row5): 批量提取DOCX文件信息并写入Excel :param folder_path: DOCX文件所在文件夹 :param extract_config: 提取配置 :param template_excel_path: 模板Excel文件路径.xls格式 :param start_row: 开始写入的行号 if not os.path.exists(folder_path): print(f❌ 文件夹路径不存在{folder_path}) return if not os.path.exists(template_excel_path): print(f❌ 模板Excel文件不存在{template_excel_path}) return # 收集所有提取结果 all_results [] file_list os.listdir(folder_path) docx_count 0 success_count 0 for file_name in sorted(file_list): # 排序确保顺序一致 if file_name.lower().endswith(.docx): docx_count 1 file_path os.path.join(folder_path, file_name) file_size os.path.getsize(file_path) / 1024 print(f\n{*10} 开始处理第{docx_count}个文件 {*10}) print(f文件名{file_name}{file_size:.1f}KB) result extract_single_docx_info(file_path, extract_config, print_rowsTrue) if result: success_count 1 # 添加文件名到结果中 result[filename] file_name all_results.append(result) print(f✅ 第{docx_count}个文件提取成功) else: print(f❌ 第{docx_count}个文件提取失败) print(f\n{*60}) print(f 提取统计共处理 {docx_count} 个文件成功提取 {success_count} 个) if all_results: # 生成输出文件名 template_dir os.path.dirname(template_excel_path) template_name os.path.basename(template_excel_path) # 获取所有成功提取的学生姓名用顿号分隔 student_names [] for result in all_results: name result.get(name, ) if name and name ! 空单元格 and 超出范围 not in str(name): # 清理姓名中可能不合法的字符 clean_name re.sub(r[\\/*?:|], , name) student_names.append(clean_name) # 生成姓名组合字符串 if student_names: if len(student_names) 1: name_str student_names[0] else: name_str 、.join(student_names) else: # 如果没有获取到姓名使用当前时间作为文件名 name_str datetime.datetime.now().strftime(%Y%m%d_%H%M%S) # 生成输出文件名 if 批量 in template_name: output_name template_name.replace(批量, f批量{name_str}) elif 批量 in template_name and in template_name: output_name template_name.replace(, f{name_str}) else: # 如果没有标准格式就在扩展名前添加 name_parts os.path.splitext(template_name) output_name f{name_parts[0]}{name_str}{name_parts[1]} output_path os.path.join(template_dir, output_name) print(f\n 输出文件名{output_name}) print(f 包含学生{、.join(student_names) if student_names else 无姓名数据}) print(f 总行数{len(all_results)} 行从第{start_row}行开始写入) # 使用win32com写入Excel if write_to_excel_with_win32com(template_excel_path, output_path, all_results, start_row): print(f\n{*10} 处理完成{*10}) print(f 输出文件{output_path}) else: print(f\n❌ Excel写入失败) else: print(f\n❌ 没有成功提取的数据) # 主程序 if __name__ __main__: # 统一配置区只需修改这里 # 基础路径配置所有路径都基于这个基础路径 BASE_PATH rD:\test\20桌面素材\20260331新长表插班生园园通上传信息提取 # Word文件所在文件夹.doc或.docx文件存放位置 WORD_FOLDER os.path.join(BASE_PATH, 00word) # 模板Excel文件路径 TEMPLATE_EXCEL os.path.join(BASE_PATH, 幼儿导入模板-XXXX.xlsx) # Excel开始写入的行号从1开始计数 START_ROW 9 # 是否先转换.doc为.docxTrue: 先转换再提取 False: 直接提取 AUTO_CONVERT_DOC True # 提取配置(行索引, 列索引, 字段名) EXTRACT_CONFIG [ (0, 1, name), # 第1行第2列 - 姓名 (0, 3, sex), # 第1行第4列 - 性别 (0, 7, Hometown), # 第1行第8列 - 籍贯 (1, 1, Ethnicity), # 第2行第2列 - 民族 (1, 5, birthday), # 第2行第6列 - 出生日期 (2, 3, birthplace), # 第3行第4列 - 出生地 (4, 1, ID), # 第5行第2列 - 身份证号 (7, 1, PPA), # 第8行第2列 - 户籍地址(根据地址手选省市区) (8, 1, PNC), # 第9行第2列 - 户籍居委 (7, 1, PPA2), # 第8行第2列 - 户籍地址 (12, 1, RA), # 第13行第2列 - 居住地址 (13, 1, RNC), # 第14行第2列 - 居住地居委 (12, 1, RA2), # 第13行第2列 - 居住地址(根据地址手选省市区) (18, 0, parent), # 第19行第1列 - 监护人关系 (18, 1, parentname), # 第19行第2列 - 监护人姓名 (18, 3, IDparent), # 第19行第4列 - 监护人ID (18, 6, phone) # 第19行第7列 - 手机号 ] # 执行程序 print(*60) print( 插班生信息提取工具) print(*60) print(f 工作目录{BASE_PATH}) print(f Word文件夹{WORD_FOLDER}) print(f 模板文件{TEMPLATE_EXCEL}) print(f 开始行号{START_ROW}) print(f 自动转换DOC{是 if AUTO_CONVERT_DOC else 否}) print(*60) # 第一步如果需要将.doc转换为.docx if AUTO_CONVERT_DOC: print(\n 步骤1检查并转换.doc文件为.docx格式) print(-*40) batch_convert_doc_to_docx(WORD_FOLDER) # 第二步提取信息并写入Excel print(\n 步骤2从DOCX文件提取信息并写入Excel) print(-*40) batch_extract_and_write_to_excel(WORD_FOLDER, EXTRACT_CONFIG, TEMPLATE_EXCEL, START_ROW) print(\n✨ 程序执行完毕)把原始的doc改成DOCX便于提取需要的内容提取到新版长表中EXCLE文件名包含学校插班学生姓名重新对应需要的内容内容都是一一对应了。然后把文字信息复制到第6行白行上范例的日期是“-”但是下面的日期虽然显示“-”但上面方框是“/”我复制范例日期2025-09-01到下面白行上改成02立刻就变成了/的样式因为没法设置“文本”“日期”格式所以只能手动输入“XXXX-XX-XX” 。大部分内容都是从第一行复制到第6行后面的内容也是这样一一对应1.要么复制第二行内容到第五行2.要么把最下面的信息复制到第5行3.要么手打“如班级托大班一班”4.要么手选序列按钮之后清空第7行之后的内容长表做好了上传OA本次的新长表比旧长表没有隐藏列有范例二表合一更清晰更方便。