
如果每天有一堆 Word 文档要改格式、抽数据、生成合同手动操作做到手软那这次要整理的这套 Python 办公自动化方案可以直接用起来。我们围绕py100--lv2-085办公自动化-word-doc这个项目场景把Python 操作 Word 文档的完整技术链路拆开讲一遍从环境准备、docx 文件读写、批量处理到常见坑和性能优化全部落到可执行的代码上。这篇文章不是泛泛讲概念而是直接给你能跑的脚本思路。你不需要一开始就理解所有 API先照做跑通之后再按自己的业务改。1. 核心能力速览能力项说明项目类型Python 办公自动化实战围绕 Word 文档处理核心依赖python-docx操作 .docx 文件补充依赖pandas、openpyxl、pypdf、win32com 或 spire.doc 等主要功能读取 Word 内容、批量生成文档、修改样式、表格处理、页眉页脚、目录操作文件范围仅支持 .docx旧版 .doc 建议先另存为 .docx或用 COM 组件桥接支持平台Windows / macOS / Linux启动方式Python 脚本运行或封装成命令行工具是否支持 API不依赖网络可通过 FastAPI 封装为本地 Office 文档接口服务是否支持批量任务支持脚本 循环 文件目录即可适合场景合同生成、报告填写、试卷排版、周报汇总、DOCX 内容抽取、知识库文件预处理从直观感受说用 Python 做 Word 自动化真正的价值不在于“能打开一个 docx”而在于“几十个文档几十种格式一条命令跑完”。如果你是经常处理 Word 的运营、测试、开发或文档工程师这篇文章可以直接收藏备用。2. 适用场景与使用边界2.1 适合做的事模板填充把合同、报价单、简历、通知做成模板用占位符替换关键字段批量生成多个版本。内容抽取从大量 Word 文档里提取标题、段落、表格喂给知识库或者转 Excel 做统计分析。格式规范统一正文小四宋体、标题黑体、行距固定值无需手动逐篇调整。批量新建新建学期计划、周报文件、会议纪要并生成规范的目录结构。自动汇稿汇总各部门提交的 docx 片段拼成一份总文档。2.2 不适合做什么.doc老格式python-docx 不支持阅读旧文档需要先转成.docx或在 Windows 上走 Office COM 接口。复杂排版美化Word 的文本框、SmartArt、复杂公式渲染python-docx 覆盖有限这类需求更适合 VBA 或人工处理。高保真 PDF 转换Word 转 PDF 首选 Office 自身导出、LibreOffice或商业库不应直接用 python-docx 硬转。需要宏执行、修订跟踪、多人协同的深度功能不建议纯开源库硬顶。2.3 文档版权与隐私边界处理公司合同、内部报告、用户信息时注意两点不要让脚本把敏感文本打印到日志或网络请求中。公有云或第三方服务只传脱敏后的数据本地脚本优先数据不出内网最稳妥。3. 环境准备与前置条件3.1 基础检查建议 Python 3.9 以上版本先用命令确认当前环境python --version pip --version如果还没有 Python需要先安装并配置环境变量。安装时注意勾选Add Python to PATH否则后面命令容易提示找不到 python。3.2 创建虚拟环境避免依赖冲突建议项目目录下单独建虚拟环境mkdir word_auto cd word_auto python -m venv venvWindows 激活虚拟环境.\venv\Scripts\activatemacOS/Linux 激活 virtualenvsource venv/bin/activate3.3 安装依赖pip install python-docx pandas openpyxl pypdf核心推荐只有python-docx。后三个用于批量抽取后数据整理先装上省得跑的时候再补。Windows 使用者如果希望实现对旧版.doc或 Word 导出 PDF 的高度控制也可以安装pip install pywin32这会调用本机 Office COM 组件。优点是“所见即所得”缺点是不能脱离 Windows 和 Office 环境。3.4 语言版本与系统兼容性操作系统层面很宽松python-docx是纯 Python 库不依赖 Office 软件本身。因此在没有安装 Word 的 Linux 服务器上照样可以生成和读取.docx这很适合跑定时批量任务。涉及win32com的场景则必须Windows 安装了 Microsoft OfficePython 是 64 位或 32 位与 Office 对应否则 COM 注册表无法正确加载3.5 目录结构规划建议一个项目一个目录分开管理输入、输出、脚本和备份word_auto/ ├─ inputs/ # 原始 docx 放这里 ├─ outputs/ # 脚本生成的结果 ├─ templates/ # 带占位符的模板 ├─ scripts/ # py 脚本 └─ logs/ # 批量任务日志4. python-docx 安装与最快上手验证完成环境准备后先跑一个最小示例确认库能正常读取和写入 docx。pip install python-docx然后创建scripts/hello_word.pyfrom docx import Document doc Document() doc.add_heading(Python 操作 Word 测试, level0) doc.add_paragraph(这是 python-docx 创建的第一个段落。) output_path ../outputs/hello.docx doc.save(output_path) print(已生成:, output_path)运行cd scripts python hello_word.py打开../outputs/hello.docx能看到一个文档标题和一行正文说明环境闭环已经打通。注意Document()默认创建空白文档它并不是基于某个模板所以字体、页边距、样式需要后续单独设置。5. 读取 Word 文档的内容自动化办公最常见需求之一把 docx 中的段落、表格、样式信息读取出来。5.1 读取段落from docx import Document doc Document(../inputs/示例文档.docx) for i, para in enumerate(doc.paragraphs): print(i, para.style.name, |, para.text)这里可以看到每个段落的样式名称比如Normal、Heading 1、List Paragraph。5.2 读取表格Word 里很多数据在表格中需区分 body 段落和表格对象for idx, table in enumerate(doc.tables): print(f--- 表格 {idx} ---) for row in table.rows: cells [cell.text.strip() for cell in row.cells] print( | .join(cells))5.3 按标题抽取正文如果需要按一级、二级标题抽内容可以做简单规则判断from docx import Document doc Document(../inputs/示例文档.docx) for para in doc.paragraphs: if para.style.name.startswith(Heading): level para.style.name.split()[-1] print(f{ * (int(level) - 1)}[标题{level}] {para.text})5.4 读取时需要小心的坑空段落很多建议过滤para.text.strip()为空的行。表格里还有单元格合并row.cells会出现同一个 cell 对象重复引用需要手动去重。图片和文本框para.text不包含文本框内文字文本框本质是独立浮层或内嵌图形。页眉页脚内容doc.paragraphs不包含页眉页脚需要单独读取section doc.sections[0] header section.header for para in header.paragraphs: print(页眉:, para.text)这些细节在转知识库或做文本预处理时特别重要容易被忽略。6. 创建与修改 Word 文档6.1 标题与样式切换from docx import Document from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.shared import Pt doc Document() doc.add_heading(一级标题, level1) doc.add_heading(二级标题, level2) p doc.add_paragraph(正文内容居中对齐) p.alignment WD_ALIGN_PARAGRAPH.CENTER run p.add_run(这句话加粗并变红) run.bold True run.font.size Pt(14) run.font.color.rgb RGBColor(255, 0, 0)需要注意的是add_heading(...)使用的标题样式默认带蓝色如果公司规范要求纯黑需要覆盖主题样式。一个add_paragraph返回的Paragraph对象可以挂多个Run不同Run使用不同格式。6.2 设置中文字体与英文字体python-docx 设计上偏向英文文档结构中文处理时必须同时设置西文字体和eastAsia字体避免中文字体不生效from docx.shared import Pt from docx.oxml.ns import qn run p.add_run(中文宋体小四) run.font.size Pt(12) run.font.name Times New Roman r run._element.rPr.rFonts r.set(qn(w:eastAsia), 宋体)解析run.font.name Times New Roman设置 ASCII 字体。中文字体通过 XML 元素设置w:eastAsia为“宋体”。实际 Word 文档中的“字体”概念包含拉丁字体和东亚字体两层只用 Python 属性设置会看到中文仍然显示默认字体。6.3 表格创建doc Document() table doc.add_table(rows3, cols3) table.style Table Grid # 表头 table.cell(0, 0).text 编号 table.cell(0, 1).text 姓名 table.cell(0, 2).text 部门 # 填充数据 data [ [001, 张三, 开发部], [002, 李四, 测试部], ] for row_idx, row_data in enumerate(data, start1): for col_idx, value in enumerate(row_data): table.cell(row_idx, col_idx).text value doc.save(../outputs/表格示例.docx)创建表格后常用自定义合并单元格设置列宽单元格底色垂直对齐方式6.4 设置表格列宽与单元格底色浅搜索一下“python 设置 word 表格宽度”“word 表格双线变单线”可以知道表格列宽在 Word 里是一个比较复杂的概念涉及tblW、tblLayout、单元格级tcW三层单纯对cell.width赋值并不总是在页面显示中生效。基本方式是from docx.shared import Cm table.autofit False table.columns[0].width Cm(2) table.columns[1].width Cm(4) table.columns[2].width Cm(6)配合单元格边框设置或去掉表格线需要操作 XMLfrom docx.oxml.ns import qn from docx.oxml import OxmlElement def set_table_border(table, **kwargs): tbl table._tbl tblPr tbl.tblPr if tbl.tblPr is not None else OxmlElement(w:tblPr) borders OxmlElement(w:tblBorders) for edge in (top, left, bottom, right, insideH, insideV): element OxmlElement(fw:{edge}) element.set(qn(w:val), kwargs.get(edge, single)) element.set(qn(w:sz), kwargs.get(sz, 4)) element.set(qn(w:color), kwargs.get(color, 000000)) borders.append(element) tblPr.append(borders)这个函数看起来繁琐但批量处理时很好用尤其需要把“双线变单线”时直接把w:val改成single即可。6.5 页边距、纸张和页眉页脚section doc.sections[0] section.page_height Cm(29.7) section.page_width Cm(21.0) section.top_margin Cm(2.54) section.bottom_margin Cm(2.54) section.left_margin Cm(3.18) section.right_margin Cm(3.18)页眉页脚自带一个paragraph容器可以向其中添加文本。设置“首页不同”通过section.different_first_page_header_footer开关。6.6 添加页码页码是 python-docx 中需要插入 XML 的典型需求footer section.footer p footer.paragraphs[0] p.text run p.add_run() fldChar1 OxmlElement(w:fldChar) fldChar1.set(qn(w:fldCharType), begin) instrText OxmlElement(w:instrText) instrText.set(qn(xml:space), preserve) instrText.text PAGE fldChar2 OxmlElement(w:fldChar) fldChar2.set(qn(w:fldCharType), end) run._r.append(fldChar1) run._r.append(instrText) run._r.append(fldChar2)7. Word 模板批量填充实战7.1 用占位符实现模板替换核心思路准备一个带{{name}}、{{date}}、{{content}}这类占位符的 docx 模板然后用replace_paragraph_text替换。from docx import Document def replace_paragraph_text(paragraph, old_text, new_text): 替换段落内包含旧文本的 run返回是否替换成功 for run in paragraph.runs: if old_text in run.text: run.text run.text.replace(old_text, str(new_text)) return True return False def fill_template(template_path, output_path, data): doc Document(template_path) for para in doc.paragraphs: for key, value in data.items(): replace_paragraph_text(para, {{ key }}, value) # 表格中的占位符也要处理 for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: for key, value in data.items(): replace_paragraph_text(para, {{ key }}, value) doc.save(output_path) if __name__ __main__: data { name: 张三, department: 研发中心, date: 2025-06-01, content: 经协商同意请假 3 天。, } fill_template(../templates/请假条模板.docx, ../outputs/请假条_张三.docx, data)7.2 最简单但最规范的替代方案运行级别替换上面代码中run.text很可能只有“{{name}}”这个字段的一部分Word 在保存时可能把一句话拆成多个 run导致if old_text in run.text判断失败。在模板制作时建议先在源码里写好完整占位符比如申请人{{name}} 部门{{department}}然后脚本方法一是把段落内所有 run 的文字拼接起来统一替换再写回第一个 run清空其他 rundef replace_keep_style(paragraph, data): full_text .join(run.text for run in paragraph.runs) if {{ not in full_text: return for key, value in data.items(): full_text full_text.replace({{ key }}, str(value)) if paragraph.runs: paragraph.runs[0].text full_text for run in paragraph.runs[1:]: run.text 这种方式会丢失原先各 run 间的局部格式差异比如一个字段内一半粗体一半正常。如果占位符本身就是独立字段不会遇到这种问题。7.3 批量生成合同/报告批量任务的核心是读 Excel 或读取 JSON 数据文件。我们用openpyxl读入一个合同清单import openpyxl from pathlib import Path wb openpyxl.load_workbook(../inputs/合同清单.xlsx, data_onlyTrue) ws wb.active for row in ws.iter_rows(min_row2, values_onlyTrue): name, company, project, amount row data { name: name, company: company, project: project, amount: f{amount:.2f}, } output_path Path(../outputs/合同) / f{name}_{project}.docx fill_template(../templates/合同模板.docx, str(output_path), data) print(生成:, output_path)一个细节如果任务是给每人生成一份 Word 并把链接转发出去就不要在脚本里带流量日志只打印文件名即可。7.4 合并多个 Word 段落如果要把多个 Word 拼成一个总文档不能直接Document(a.docx).add_paragraph()把整个文件塞进去需要逐段复制def append_docx(src_path, dst_doc): src_doc Document(src_path) for para in src_doc.paragraphs: new_para dst_doc.add_paragraph() # 复制文本 for run in para.runs: new_run new_para.add_run(run.text) # 复制基础格式 new_run.bold run.bold new_run.italic run.italic new_run.font.size run.font.size # 表格合并 for table in src_doc.tables: new_table dst_doc.add_table(rowslen(table.rows), colslen(table.columns)) for row_idx, row in enumerate(table.rows): for col_idx, cell in enumerate(row.cells): new_table.cell(row_idx, col_idx).text cell.text如果对样式还原要求不高这个够用。如果要求精确复刻复杂页面布局建议用 Office 自带的“插入对象 文件中的文字”或者通过 COM 自动化操作实现。8. 批量改名与批量打开另存8.1 批量重命名 .docx 文件办公自动化中新生成文件后往往需要改文件名。例如把“周报.docx”改成“周报_姓名_日期.docx”from pathlib import Path import datetime src_dir Path(../outputs) today datetime.date.today().strftime(%Y%m%d) for file in src_dir.glob(*周报.docx): new_name file.with_name(f{file.stem}_张三_{today}.docx) file.rename(new_name) print(new_name)8.2 Word 与 PDF常见链路如果把.docx批量转 PDF更通用和可控的有两条路Windows Office COM 组件pip install pywin32import os import glob from win32com.client import Dispatch word_app Dispatch(Word.Application) word_app.Visible False for file in glob.glob(../outputs/*.docx): doc word_app.Documents.Open(os.path.abspath(file)) pdf_path file.replace(.docx, .pdf) doc.SaveAs(pdf_path, FileFormat17) # 17 对应 PDF 格式 doc.Close() word_app.Quit()LibreOffice 命令行方案适合 Linux 服务器或本机安装了 LibreOffice 的环境soffice --headless --convert-to pdf --outdir ../outputs ../outputs/*.docx上面两种方案随办公自动化项目环境情况选择。无论哪种转 PDF 前必须确认涉及的文档没有版权风险、包含公司内部敏感内容时不要再外传保证转换结果只落在本机或内网服务器。9. 接口 API 与批量任务设计9.1 用 Flask/FastAPI 封装本地服务Word 自动化完全可以在无桌面环境中跑脚本但如果把它开放给团队或集成到现有系统建议封装成 HTTP 接口。# scripts/app.py from fastapi import FastAPI, UploadFile, File from filing import fill_template import uuid app FastAPI() app.post(/generate) async def generate_doc(template: UploadFile File(...), data: str Form(...)): import json data_dict json.loads(data) temp_input f/tmp/{uuid.uuid4()}.docx with open(temp_input, wb) as f: f.write(await template.read()) out_path f/tmp/{uuid.uuid4()}.docx fill_template(temp_input, out_path, data_dict) return FileResponse(out_path, filenamegenerated.docx)启动方式uvicorn app:app --host 0.0.0.0 --port 8000调用示例curl --location http://127.0.0.1:8000/generate \ --form templatetemplates/请假条模板.docx \ --form data{\name\: \王五\, \department\: \市场部\} \ --output 请假条_王五.docx如果只是自己本地测试不需要把这个接口暴露到公网尽量只绑定 127.0.0.1避免内网他人直接调用后产生批量文件。9.2 批量分钟级任务与重试策略批量任务开始时建一个logs/batch.log记录每次处理的文档名和异常次数import logging logging.basicConfig( filename../logs/batch.log, levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, ) for row in rows: try: fill_template(...) logging.info(f[OK] {name}) except Exception as exc: logging.error(f[FAIL] {name} {exc})更严格的批量任务建议先取 2 个样本跑通再全量执行。文件太多时 sleep 零点几秒避免把 Windows 文件服务打满。保存失败时单独放到“失败队列”目录不要原地覆盖。每个输出文件名加唯一序号防止同名覆盖。10. 资源占用与性能观察Python 操作 Word 文档属 CPU 密集型任务对显存、GPU 没有要求。真正影响性能的是单文档内嵌图片数量与大小表格行数和单元格数量批量任务时是否频繁创建和销毁Document对象是否在打开时操作 COM 组件10.1 内存占用批量处理 100 份小 docx一般不会造成太大压力但如果每份都嵌满高分辨率图片读取时会加载到内存批量处理时内存占用会线性上升。稳妥做法是逐份处理、逐份保存、释放变量for file in files: doc Document(file) # 处理逻辑 doc.close()需要注意python-docx的Document对象没有强制的 close 方法。从使用的角度直接在循环里复用变量再由垃圾回收机制处理即可。如果内存增长明显可以用del doc提示回收。10.2 Windows COM 资源注入如果使用win32com务必要写finally关闭文档word_app Dispatch(Word.Application) try: doc word_app.Documents.Open(path) doc.SaveAs(pdf_path, FileFormat17) finally: doc.Close(False) word_app.Quit()COM 进程没有退出时后台会残留 WINWORD.EXE多跑几次机器会很卡。排查方式tasklist | findstr WINWORD.EXE10.3 优化方向普通文本任务用 python-docx 纯内存处理比调用 Office COM 快。如果只是简单文本替换模板越简单速度越快。需要转 PDF、页码域更新推荐 COM 或 Office 原生日历。11. 常见问题与排查方法问题现象可能原因排查方式解决方案python-docx 安装失败pip 版本过旧或网络源不稳pip install -U pip切换清华源pip install python-docx -i https://pypi.tuna.tsinghua.edu.cn/simple打开 docx 中文乱码不是真正的 docx 文本问题可能文件实际是 RTF 或老 doc用记事本打开看文件头或file命令另存为标准 docx中文字体设置后不生效没有设置w:eastAsia用 Word 打开查看字体属性补充设置rFonts的eastAsia模板字段替换无效占位符被 Word 拆分到多个 Run打印所有段落的 run 文本使用段落级拼接替换方案表格列宽设置不生效autofit和表格属性冲突查看 XML 中tblLayouttable.autofitFalse后逐列设置并设置单元格宽度doc 旧格式无法打开python-docx 不支持.doc检查扩展名先用 Word/LibreOffice 转.docx或用 COM 调用 Word页码不显示域代码未插入到 Footer查看 XML 中是否包含PAGE按前文的fldChar/instrText方式插入域代码COM 后台进程残留异常退出时未调用Quit()tasklist查看WINWORD.EXE脚本内使用try/finally并word_app.Quit()保存后文件被占用Word 还在前台打开该文件检查是否有 Word 窗口关闭占用文件或换输出路径批量生成慢每个 Document 都处理图片和复杂样式统计单个文档耗时抽样测试全量批量前先小范围试跑无法读取页眉内容只遍历了doc.paragraphs调试时打印 sections使用section.header.paragraphs读取12. 最佳实践与使用建议模板先行。不推荐在代码中用add_paragraph手工排列几十页的排版细节把模板设计成“带占位符的正式版”脚本只负责替换和另存维护成本较低。统一字体设置函数。对中文正文、中文标题、表格单元格分别封装一个函数避免每个 run 都写一段w:eastAsia逻辑。def set_run_font(run, ch_font宋体, en_fontTimes New Roman, size12, boldFalse): run.font.name en_font run._element.rPr.rFonts.set(qn(w:eastAsia), ch_font) run.font.size Pt(size) run.bold bold保留一份可复现的最小测试集。新建test_data目录放 3 份极小 docx每次修改代码先跑这个目录再上全量。输入输出分离。脚本不要操作源文件目录生成结果统一放outputs失败文件收集到logs辅助问题归因。安全边界。多人共用接口时限制上传模板大小模板里不要放高危宏。虽然 python-docx 不会执行 VBA但转换流程转入 Office COM 时仍要警惕宏类文档。合规意识。涉及人脸、声音、公司 logo、内部数据时需要确认授权生成合同文件必须在业务侧经过人工复核再对外发送。13. 总结与下一步py100--lv2-085办公自动化-word-doc这个场景的完整实现思路已走完。把 python-docx 用顺后你可以做到读取 docx 段落与表格、批量生成模板文件、自动化设置中文字体、批量改名、另存 PDF以及通过 HTTP 接口让系统直接产出 Word 文档。建议先做两件事一、用自带模板跑一遍批量填充二、整理一批真实 Word 测试读取逻辑。最容易踩的坑集中在 Word XML 层比如字体需区分中西文、占位符可能被拆 Run、表格宽度受表格布局影响。下一步可以把这套脚本和 Excel 报表结合做“从数据库查合同数据 - 批量生成 Word - 自动转 PDF”的完整流水线。等稳定性验证通过后再用 FastAPI 把核心能力暴露给业务系统实现上传模板并提交数据即可拿到 docx 结果。工具是透明且可审计的核心代码都在本地没有隐式上传、没有不可控的模型链路。只要源码检查清楚自动化生成 Word 这份工作就能安全地提高办公效率。