Python按户合并Excel数据批量生成Word文档

发布时间:2026/8/27 10:15:50
Python按户合并Excel数据批量生成Word文档 在实际业务中“按户合并”可以算是数据处理里的高频需求。社区在做一户一档台账、物业整理业主信息、银行寄发对账单、天然气公司做入户安检登记时原始数据往往是从管理系统里导出的明细 Excel一行是一口人一个人一条记录。可是业务交付时需要的却是一户一份文档文档里有户主信息、家庭住址还要带成员列表。如果家庭只有两口人直接复制粘贴还能接受一旦社区有几百上千户逐个人复制到 Word 里再把样式统一既浪费时间也容易出现漏人、错行的问题。这种需求背后并不是复杂的算法而是一个数据组织问题先把明细数据按“户”分成一组再把每户的结构化数据映射到固定文档模板上。本文围绕“数据填充到固定文档”这个主题用一套 Python 脚本完整演示整个过程。文中会讲解如何用 pandas 读取并清洗 Excel 明细数据如何按户分组提取户主和成员列表如何准备 Word 模板以及如何用 docxtpl 批量生成一户一份的独立文件。如果你之前对 Word 自动化生成比较陌生也可以按本文步骤从零搭起来。阅读本文你需要具备基础的 Python 语法知识能跑通 pip 安装即可不用提前了解 pandas 或 docx 细节。先说结果读完并跑完示例后你会得到两个层面的产出。第一一个可以复用、可以直接替换成自己业务数据的批量生成脚本第二一套关于“模板占位符 数据上下文”的通用写法之后遇到按订单、按班组、按项目等相似需求都能套用同一套思路。1. 业务背景与需求分析1.1 什么是按户合并按户合并简单说就是“多人数据归到一户再根据每户的数据重新组织文档”。举个例子居民基础信息表里有张三、张三妻子、张三孩子三条记录三人的“户号”都是 H001。业务上要输出一份“居民家庭信息登记卡”包含户主姓名、地址、户籍号以及所有成员的信息表格。那么程序要做的就是把 H001 的三条记录捏合成一份独立文档再把 H002、H003 同理处理。从数据角度看源数据是一对多的关系一个户号对应多条人员明细。而目标文档是一对一的关系一“户”生成一份文档。这个关系转换是整套需求的核心。常见应用场景包括社区居委会建立“一户一档”按照居民地址逐户保存家庭成员信息物业公司在交房、年审时需要按房号建立住户档案银行或保险机构给客户家庭寄送对账单时需要把账户关联的家庭成员合并展示运营商办理家庭宽带、固话业务时需要按“客户编号”生成家庭成员登记表。这些场景的底层逻辑都是一样的源数据是按人维护的明细业务结果要按户存储成文档。1.2 为什么用 Python 做这件事很多人的第一反应是用 Excel 数据透视表把“户号”作为行字段把成员姓名合并到同一个单元格里。这个方法能解决老式台账的查看问题但有两个明显缺陷第一合并后的单元格是以逗号或换行连接的文本不能真正像 Word 表格那样展示“姓名、与户主关系、身份证号”等结构化字段第二最终交付物如果是盖章、存档的 Word 登记卡Excel 透视表无法完成。Word 自带的邮件合并也能做批量文档生成适合字段固定、长度固定的简单信函。但按户合并时每户成员数量是动态的表格行数不固定。邮件合并对“动态表格行循环”的支持非常有限模板写起来很别扭。Python 的优势在于把数据处理和文档生成分成两个独立环节pandas 负责把 Excel 明细整理成标准结构docxtpl 负责把结构填充进 Word 模板。模板和数据解耦之后后续如果模板样式要调整只需要修改模板文件不需要改动很多业务代码。这也是工程上推荐的做法数据和展示分离。2. 技术选型与整体流程2.1 不同方案对比在动手写代码之前先用一个表格对比常见方案方便你根据自己项目的情况做判断。方案优点缺点适用场景Excel 数据透视表/公式操作简单不写代码无法输出结构化 Word 表格成员数量一多就难维护只需快速查看合并结果Word 邮件合并原生支持批量文档生成动态表格行处理弱模板逻辑表达有限字段固定的简单信函Python docxtpl模板与数据分离支持动态表格行需要学习 Python 基础和占位符语法按户生成固定 Word 文档Python python-docx灵活度最高可完全控制文档结构样式逻辑硬编码在脚本里维护成本高需要高度自定义复杂文档考虑到大多数台账交付物是 Word 文档且每户成员数量不唯一本文采用“pandas docxtpl”组合。docxtpl 基于 Jinja2 模板引擎用类似 Django/Jinja2 的{{ 变量 }}语法做文本替换用{%tr for %}做表格行循环。这样既保留了 Word 模板的视觉效果又让脚本代码保持精简。2.2 整体处理流程整条链路可以拆成六个步骤读取 Excel 明细数据并做好字段类型转换与空值处理以“户号”为分组键把同一户的所有成员归为一组从每一组数据中提取户主、住址、联系电话等摘要信息把成员明细整理成模板可遍历的列表结构加载固定 Word 模板调用 docxtpl 渲染数据按户输出为独立 docx 文件并打印处理日志。后面每一章都会对应这个流程中的一部分。先把流程记在脑子里再看代码就不会觉得混乱。3. 环境准备与测试数据3.1 环境依赖本文示例使用 Python 3.8 编写在 Python 3.10 环境下验证通过。主要依赖如下pandas读取 Excel、按户分组、数据清洗openpyxlpandas 读取 xlsx 文件的底层引擎python-docx虽然主方案用的是 docxtpl但生成模板和验证输出时会用到它docxtpl加载 Word 模板完成占位符替换和表格行循环。建议在项目目录下创建独立的虚拟环境避免依赖冲突。以 Windows 为例命令行执行cd household_merge python -m venv venv venv\Scripts\activatemacOS 或 Linux 下激活命令是source venv/bin/activate然后安装依赖pip install pandas openpyxl python-docx docxtpl如果你的网络环境有代理或镜像配置问题可以用清华镜像源加速安装pip install pandas openpyxl python-docx docxtpl -i https://pypi.tuna.tsinghua.edu.cn/simple版本方面不需要刻意追求最新pandas 1.5 以上、docxtpl 0.16 以上版本都可以运行本文代码。Python 版本差异主要影响 f-string 和类型注解dtype 参数和分组逻辑在新旧版本中表现一致。3.2 项目目录结构为了保证代码清晰推荐按下面的目录组织项目household_merge/ ├── data/ │ └── 居民基础信息.xlsx ├── template/ │ └── template.docx ├── output/ ├── generate_test_data.py ├── generate_template.py └── batch_export.pydata/存放输入的 Excel 明细数据template/存放固定 Word 模板output/存放批量生成的按户文档generate_test_data.py生成一份示例数据方便你直接复现generate_template.py自动生成 Word 模板避免手动创建时占位符写错batch_export.py是核心批处理脚本。3.3 生成测试数据很多读者手头没有现成的居民 Excel所以先提供一个生成测试数据的脚本。这个脚本会构造两个家庭、共五条成员记录写入data/居民基础信息.xlsx。# generate_test_data.py import pandas as pd rows [ (H001, 张伟, 40, 户主, 370102198501011234, 13800001234, 幸福小区1号楼101), (H001, 王丽, 38, 配偶, 370102198703015678, 13800005678, 幸福小区1号楼101), (H001, 张小明, 10, 子女, 370102201505012345, , 幸福小区1号楼101), (H002, 李强, 55, 户主, 370102197001011111, 13900001111, 幸福小区2号楼302), (H002, 李芳, 27, 子女, 370102199801012222, 13900002222, 幸福小区2号楼302), ] df pd.DataFrame(rows, columns[户号, 姓名, 年龄, 与户主关系, 身份证号, 联系电话, 家庭住址]) df.to_excel(data/居民基础信息.xlsx, indexFalse) print(测试数据已生成data/居民基础信息.xlsx)注意这里刻意把“年龄”也加进来了但后续模板展示不强制使用。年龄在业务上容易变化建议正式存档时以“出生日期”代替“年龄”更稳妥。示例数据重点演示的是户号、姓名、与户主关系、身份证号、联系电话这些固定字段。运行脚本前先确保data目录存在mkdir data template output python generate_test_data.py生成的 Excel 打开后应该是普通表格H001 三行、H002 两行。这里有个容易被忽略的细节身份证号在 Excel 中默认会显示成科学计数法而to_excel保存的 DataFrame 如果列类型是字符串Excel 打开时通常会保留文本格式。如果你用自己业务导出的 Excel务必在源文件里把身份证号、户号列设置为“文本”格式避免精度丢失。4. 数据读取与按户分组4.1 读取 Excel 时的字段类型处理按户合并的第一步是把 Excel 表格读进 pandas DataFrame。这里最容易踩的坑就是“字段类型”。身份证号、联系电话、户号这类字段如果 pandas 默认按数值解析18 位身份证号会变成科学计数法最后几位变成 0户号如果是001这种带有前导零的编码默认解析为数字后会丢掉前导零。解决方案是在pd.read_excel里通过dtype参数显式指定这些列按字符串读取。import pandas as pd df pd.read_excel( data/居民基础信息.xlsx, dtype{ 户号: str, 身份证号: str, 联系电话: str, }, )这样做的好处是身份证号、手机号、户号都能保持原样。读取后还要把列名两边的空格去掉防止列名不一致导致后续df[姓名]访问报 KeyError。df.columns [c.strip() for c in df.columns]实际业务 Excel 中经常会出现列名带空格、全角空格或者前后不可见字符统一做 strip 是成本最低的防御性写法。4.2 清洗与分组清洗逻辑包含几个常见动作去掉没有户号的行、把空值统一填充为字符串、去掉文本两边的空格。户号是分组键如果某一行的户号是空值这行数据就无法归属到任何一户直接过滤掉更安全。df df.dropna(subset[户号]) df[户号] df[户号].astype(str).str.strip() df[姓名] df[姓名].fillna().astype(str).str.strip() df[与户主关系] df[与户主关系].fillna().astype(str).str.strip() df[身份证号] df[身份证号].fillna().str.strip() df[联系电话] df[联系电话].fillna().str.strip() df[家庭住址] df[家庭住址].fillna().astype(str).str.strip()为什么这里反复强调fillna()因为 pandas 在读取空单元格时默认会填充为 NaN。NaN 是浮点数 NaN如果直接astype(str)会被转换成字符串nan最后模板里就会出现奇怪的nan字样。所以必须先fillna()再转字符串。这是一个非常简单但非常关键的顺序问题。分组使用groupbygrouped df.groupby(户号, sortFalse)sortFalse表示保持 Excel 原始顺序。如果你的业务希望按户号排序输出可以去掉这个参数或者先对 DataFrame 做sort_values(户号)再分组。排序在文件多的时候更利于人工对照查找。4.3 提取户信息和成员列表分组后每一组就是“一户”。我们需要从这一户里提取两类信息。第一类是户级摘要信息包括户主姓名、户主联系电话、家庭住址。正常情况下源数据里“与户主关系”为“户主”的那个人就是这户的代表。所以优先按这个条件筛选。第二类是成员明细用于模板中的表格循环。每个成员是一个字典包含姓名、与户主关系、身份证号、联系电话等字段。def build_context(household: pd.DataFrame) - dict: head_rows household[household[与户主关系] 户主] if head_rows.empty: head_row household.iloc[0] else: head_row head_rows.iloc[0] members [] for _, row in household.iterrows(): members.append({ name: row[姓名], relation: row[与户主关系], idcard: row[身份证号], phone: row[联系电话], }) return { head_name: head_row[姓名], phone: head_row[联系电话], address: head_row[家庭住址], members: members, }这里有一个业务陷阱如果某个户号下连一个“户主”都没有那要么是数据录入不规范要么是源数据里缺少户主行。此时程序会退而取第一条记录作为户主信息并且最好在日志中给出警告。反过来如果一户里出现多个“户主”也应该提醒核对因为这说明数据源本身可能重复或错乱。这些问题在代码里未必会立刻报错但会在最终文档中体现为逻辑错误所以数据校验这一步不能省。iterrows()遍历组内每一行每次返回索引和 Series。这里我们不关心索引直接通过列名取值组装成字典列表。模板渲染时

相关新闻