news 2026/5/23 1:34:51

【办公类-142-04】20260330插班生word转长表EXCLE(4)新表重制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【办公类-142-04】20260330插班生word转长表EXCLE(4)新表重制

背景需求:

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✨ 程序执行完毕!")

把原始的doc改成DOCX,便于提取需要的内容

提取到新版长表中,EXCLE文件名包含学校(插班学生姓名)

重新对应需要的内容

内容都是一一对应了。

然后把文字信息复制到第6行(白行上)

范例的日期是“-”,但是下面的日期虽然显示“-”,但上面方框是“/”

我复制范例日期2025-09-01到下面白行上

改成02,立刻就变成了"/"的样式

因为没法设置“文本”“日期”格式,所以,只能手动输入“XXXX-XX-XX” 。

大部分内容都是,从第一行复制到第6行

后面的内容也是这样一一对应

1.要么复制第二行内容到第五行

2.要么把最下面的信息复制到第5行

3.要么手打“如班级:托大班一班”

4.要么手选序列按钮

之后清空第7行之后的内容

长表做好了,上传OA

本次的新长表比旧长表,没有隐藏列,有范例,二表合一,更清晰,更方便。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 1:34:47

116. 为项目监控员生成的警报添加标签

Procedure 程序To label alerts for Project Monitors, you must configure the Prometheus Federator Helm charts values section. This is done by adding additionalRuleLabels under defaultRules within helmProjectOperator. You can perform this modification during…

作者头像 李华
网站建设 2026/5/23 1:34:49

解决FTPS连接问题:从握手失败到成功连接的实战

前言 在处理FTP(文件传输协议)服务器的连接时,FTPS(FTP over SSL/TLS)作为一种安全的传输方式非常重要。然而,有时候我们会遇到SSL/TLS握手失败的问题。本文将通过一个实际案例,展示如何诊断并解决这些常见的问题。 案例背景 假设我们正在编写一个Python应用程序,该…

作者头像 李华
网站建设 2026/5/23 1:34:56

高效精简AMD显卡驱动:Radeon Software Slimmer全指南

高效精简AMD显卡驱动&#xff1a;Radeon Software Slimmer全指南 【免费下载链接】RadeonSoftwareSlimmer Radeon Software Slimmer is a utility to trim down the bloat with Radeon Software for AMD GPUs on Microsoft Windows. 项目地址: https://gitcode.com/gh_mirror…

作者头像 李华
网站建设 2026/5/23 1:34:54

Next.js渲染模式实战:如何为电商首页选择SSR还是SSG?附性能对比

Next.js电商渲染模式实战&#xff1a;SSR与SSG的性能博弈与选型策略 电商平台的首页加载速度每提升100毫秒&#xff0c;转化率就能增加1%——这个数字在竞争激烈的电商行业意味着数百万美元的营收差异。作为Next.js开发者&#xff0c;我们手握SSR和SSG两把利剑&#xff0c;但如…

作者头像 李华