背景需求:
4月份转了两位插班生进来
之前做了一份word内容提取到excel的代码
【办公类-142-03】20260304插班生word转长表EXCLE(3)从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转新版长表EXCLE(0604读取每行,去重,获取指定行指定索引内容 元素内部空格去掉)(多人)读取每行去重提取索引内部空格删除复制模版添加名字写入指定的格子内 有三行参考内容,虚线底纹 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, FileFormat=12) 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(r'<br\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_rows=True): """ 从单个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, start=1): 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_row=5): """ 使用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, start=start_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(SaveChanges=False) 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_row=5): """ 批量提取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_rows=True) 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 = r"D:\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为.docx(True: 先转换再提取, 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✨ 程序执行完毕!")