资讯动态

python-docx 离线安装与 Word 文档自动化实战指南

发布时间:2026/10/26 11:38:59 来源:尧图企业网站定制
简介Python-docx三方库安装包面向需要在Linux、Windows或macOS上以编程方式创建、编辑和读取.docx文档的Python开发者解决不依赖Microsoft Office即可批量处理Word文档的问题适合从入门练习到项目集成的不同技能层级。压缩包共1209个文件约11.6MB以548个py源码与测试脚本、144个rst文档、118个feature测试用例、94个xml配置及78个docx示例文档为主并包含少量图片与构建配置文件较完整地呈现了库的代码、文档和验证素材。已有11405人学习下载说明其在文档自动化场景中具有较高关注度。读者可从中获得可直接安装使用的库包以及围绕标题、段落、页眉页脚、图片、表格、样式主题等操作的源码参考和示例文档便于理解Word文档对象模型、复现常见读写流程并对照测试用例排查跨平台使用中的依赖与兼容性问题。1. 装完就能省下一下午python-docx 到底替你做了什么很多人第一次接「批量生成 Word 报告」的需求第一反应是打开 COM 调用 Office结果脚本跑到服务器上直接报错因为那台机器根本没装 Office。python-docx 解决的正是这个尴尬它是一个纯 Python 的三方库跨平台读写 .docxWindows、Linux、macOS 上行为一致不依赖本机 Word 进程也不需要 headless 环境。输入是模板文档或空文档输出仍是标准 .docx用 WPS、Office、在线预览打开都不会变形。它适合三类人要把数据库结果批量灌进固定格式合同的、要从上百份 Word 里抽取表格做汇总的、要拿 Python 直接吐一份带样式报告的。本文围绕这份安装包讲清楚装法、核心对象模型、样式与表格的实操、以及几个让人翻车的边界。注意它只管 .docx不碰 .doc 老格式也做不了 Word 里的复杂排版自动化这一点先立住后面才不会白折腾。2. 安装与环境核对把包放对地方比装完更重要2.1 选型理由为什么是 python-docx 而不是别的路批量处理 Word常见有三条路。第一条是 COM 自动化依赖本机 Office只能在 Windows 上跑进程容易僵死第二条是直接按 OOXML 规范手写 XML灵活但成本极高改一个样式要翻半天规范第三条就是 python-docx它在 OOXML 上包了一层对象模型Document、Paragraph、Run、Table这套抽象覆盖了日常 90% 的场景。代价是它不做渲染页面上「看起来怎样」它管不了只负责把结构写对。所以选型判断很简单需求是「按模板批量生成 / 批量读取结构」选它需求是「精确控制分页位置、页眉首页不同、复杂域代码」它会让你难受得考虑别的手段。安装包这种分发形式主要是应对内网机器不能直连软件源的情况把 whl 或源码包带过去离线安装。装之前先确认版本环境这是最容易偷懒也最容易出事的一步。2.2 离线安装的操作步骤拿到安装包后先看目录里有什么常见是.whl文件或python-docx-x.y.z.tar.gz源码包。先核对 Python 版本再执行安装。依赖项lxml是原生扩展不同平台、不同 Python 版本的轮子不通用这一步错了后面全是玄学报错。# 先看本机 Python 版本和位数决定用哪个轮子 python -V # 进入安装包所在目录离线安装不访问网络 cd ./python-docx-pkg python -m pip install --no-index --find-links. python-docx # 如果只有源码包先解压再本地安装 tar -xzf python-docx-*.tar.gz cd python-docx-*/ python -m pip install --no-index --find-links.. .--no-index表示完全不查在线源--find-links.表示只在当前目录找包两个参数配合才是真正的离线。少了--no-indexpip 仍可能去连源内网环境下会卡在超时上。源码包装完要留意有没有编译告警lxml编译失败通常意味着本机缺 C 编译工具链或 libxml2 头文件。2.3 装完先跑一个最小验证不要装完就往下写业务代码先花一分钟确认库能被导入、能正常读写。import docx # 打印库版本确认装的不是残留的旧版 print(python-docx version:, getattr(docx, __version__, unknown)) # 建一个文档写一段话存盘并重新读回 doc docx.Document() doc.add_paragraph(安装验证这一行能被读回来说明链路通了) doc.save(verify.docx) back docx.Document(verify.docx) print(段落数:, len(back.paragraphs)) print(首段内容:, back.paragraphs[0].text)判断标准很直接版本号能打出来、verify.docx能被生成、重新读取后段落数和文本对得上。任何一步失败问题都在环境层不要在业务代码里找原因。我一般还会顺手pip show python-docx看安装路径确认它装在当前解释器对应的 site-packages 下而不是另一个 Python 环境里——用虚拟环境时这个坑出现频率很高。3. 文档对象模型Paragraph 和 Run 是绕不开的两层3.1 结构先理清文档树长什么样python-docx 的模型是分层的。Document是根下面挂若干Paragraph段落和Table表格段落内部再挂Run文本片段。关键点在于样式的最小作用单位是 Run不是 Paragraph。同一个段落里「前半句加粗、后半句正常」这种效果靠的就是两个 Run 分别设属性。很多人一上手就写paragraph.bold True发现整段都粗了或者干脆报错原因就在这里。读取方向也按这层来先遍历doc.paragraphs再遍历paragraph.runs取样式信息。写入方向则相反先在段落里add_run(文本)再对 Run 设bold、font.size、font.color.rgb等。理解这层嵌套后面样式和表格的操作才不是背 API。3.2 读把一份文档的结构抽出来下面的脚本把段落文本、样式名、以及每个 Run 的加粗状态打出来用来摸清一份陌生文档的构成。from docx import Document from docx.shared import Pt doc Document(input.docx) # 只遍历顶层段落表格里的段落不在 doc.paragraphs 中 for i, para in enumerate(doc.paragraphs): if not para.text.strip(): continue # 空段落跳过文档里常有大量空行占位 print(f[{i}] style{para.style.name!r} text{para.text[:40]!r}) for run in para.runs: # bold 可能是 None表示继承样式不等于 False print(f run bold{run.bold} size{run.font.size} text{run.text[:20]!r})para.style.name拿到的是段落样式名比如Normal、Heading 1、自定义样式名这是后续按样式筛选内容的基础。注意run.bold可能是None含义是「继承父级样式」不是「不加粗」直接当布尔用会判断错。run.font.size返回的是Pt对象或None打印时要留意比较大小得先和Pt(12)这类对象比别直接和数字比。3.3 写从零生成一篇结构化文档写入侧的重点是「按结构建、按层级设样式」别一段一段硬塞。from docx import Document from docx.shared import Pt, RGBColor doc Document() doc.add_heading(月度运行报告, level0) # level0 是标题样式 # 小标题用内置 Heading 1便于后期生成目录 doc.add_heading(一、总体情况, level1) p doc.add_paragraph() r1 p.add_run(本周期共处理任务 ) r2 p.add_run(1280) r2.bold True # 只对数字加粗 r3 p.add_run( 条成功率 99.2%。) r3.font.size Pt(10.5) # 字号用 Pt 对象不能传浮点数 # 单独一段设置整段样式 note doc.add_paragraph(注数据统计口径与上期一致。, styleIntense Quote) note.paragraph_format.space_before Pt(6) note.paragraph_format.space_after Pt(6) doc.save(report.docx)三个细节值得记住。第一add_heading(level0)生成的是文档标题样式level1才是正文一级标题写反了目录会乱。第二font.size必须传Pt对象r3.font.size 10.5不会报错但也不会生效这是典型的静默失败。第三style传的样式名必须在文档模板里存在传错会抛KeyError内置样式名可以用自定义样式名建议先用[s.name for s in doc.styles]打出来核对。4. 样式、表格与占位符填充真实报告场景的三板斧4.1 模板占位符替换别用 str.replace 硬来最常见的需求是「拿一份模板把里面的{{name}}换成实际值」。新手容易直接读全文再 replace 回去但那样会丢掉所有格式因为文本重写后 Run 的样式就没了。正确做法是遍历段落里的 Run 做替换接受一个前提占位符不能跨 Run 断裂。from docx import Document def fill_placeholders(doc, mapping): 在段落和表格中替换 {{key}} 占位符尽量保留原有 Run 样式 def replace_in_paragraph(para): for run in para.runs: if {{ not in run.text: continue for key, value in mapping.items(): run.text run.text.replace({{ key }}, str(value)) for para in doc.paragraphs: replace_in_paragraph(para) # 表格里的文本不在 doc.paragraphs 中必须单独遍历 for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: replace_in_paragraph(para) doc Document(template.docx) fill_placeholders(doc, {name: 张三, month: 2024-06, total: 1280}) doc.save(filled.docx)逻辑说明按 Run 替换的前提是单词占位符完整落在一个 Run 里。Word 在编辑过程中经常把一个词拆成多个 Run导致{{name}}变成{{name}}这时按 Run 替换会失效。所以更稳的做法是先检测再合并遍历段落把连续 Run 的文本拼起来看是否包含占位符命中后把这段文本整体写入第一个 Run清空其余 Run 的文本。代价是合并范围内样式统一但对模板填充场景通常可接受。参数方面mapping的 value 一律str()转型因为replace只接受字符串数字和日期最好在外面先格式化别指望库帮你处理。4.2 表格读写两侧的重点不同表格操作里读的难点在合并单元格写的难点在样式继承。from docx import Document from docx.shared import Pt doc Document() # 建表时给行列数style 传内置表格样式名 table doc.add_table(rows1, cols3) table.style Table Grid # 带边框的样式不加这句默认无框线 hdr table.rows[0].cells for i, title in enumerate([项目, 数量, 备注]): hdr[i].text title for para in hdr[i].paragraphs: for run in para.runs: run.bold True # 追加数据行add_row 返回整行按 cell 索引赋值 for name, num, memo in [(甲, 120, 正常), (乙, 80, 延迟)]: cells table.add_row().cells cells[0].text name cells[1].text str(num) cells[2].text memo doc.save(table.docx)写侧的坑在样式table.style传错名字会抛KeyErrorTable Grid是最常用的内置名设了单元格text之后原来单元格里 Run 的格式会被重置所以先赋文本再设格式的顺序不能反。读侧的坑在合并单元格——table.rows[i].cells在存在横向合并时会返回重复的_Cell对象同一个合并区域会被多次读出做统计前必须去重。常见做法是用cell._tc这个底层元素做身份判断或者按行去重后再计数。4.3 段落格式与分页能做和不能做段落级参数集中在paragraph.paragraph_format上常用的是space_before、space_after、line_spacing、first_line_indent、alignment。from docx import Document from docx.shared import Pt, Cm from docx.enum.text import WD_ALIGN_PARAGRAPH doc Document() p doc.add_paragraph(这是首行缩进两字符、段前 6 磅、两端对齐的正文段落示例。) pf p.paragraph_format pf.first_line_indent Cm(0.74) # 约等于两字符按实际字号微调 pf.space_before Pt(6) pf.space_after Pt(6) pf.alignment WD_ALIGN_PARAGRAPH.JUSTIFY # 强制分页给段落加分页属性而不是插一堆空行 doc.add_paragraph(下一页从这里开始).paragraph_format.page_break_before True doc.save(format.docx)page_break_before True是分页的正确做法比插空行可靠得多空行数量会随字体和页边距漂移。但要清楚它的局限它控制的是「段落前分页」做不到「内容超长时在指定位置自动缩排」这类排版逻辑。所有涉及渲染结果的需求都得生成后用 Word 打开人工核一遍库本身不负责渲染这是它的能力边界不是 bug。5. 避坑清单五个真会卡住你的问题5.1 报 PackageNotFoundError 或找不到 docx 模块现象是import docx直接失败或者装完提示成功但导入报错。原因通常是解释器错位pip 装到了系统 Python而你在虚拟环境里跑或者反过来。解决步骤是先用python -c import sys; print(sys.executable)打出当前解释器路径再用python -m pip show -f python-docx看安装位置两者不在同一环境就重装。全程用python -m pip前缀能规避大部分这类问题。5.2 离线安装卡在 lxml 编译现象是源码包装到一半报编译错误提示缺 libxml2 或 gcc。原因在于lxml是原生扩展没有匹配的预编译轮子时 pip 会退化成源码编译。解决分两条路优先在能联网的机器上按目标平台下载对应 whl注意 Python 版本和系统架构都要对上拷进安装包目录一起装实在要编译就先装好系统级的编译工具链和 libxml2 开发包。血泪经验是别在目标机上临时补依赖离线环境里这一步经常缺东西。5.3 中文乱码或字体不生效现象是生成的文档中文显示成方块或者设了font.name但中文部分没变。原因是 Word 对中西文字体分开管理font.name只设了西文。常见做法是同时设置东亚字体通过底层元素补上from docx.oxml.ns import qn run.font.name Times New Roman run._element.rPr.rFonts.set(qn(w:eastAsia), 宋体)_element是底层 OOXML 节点rPr.rFonts存在的前提是该 Run 已经有字体属性必要时先设font.name触发节点创建。这一步属于「绕过封装」能用但要清楚它不在公开 API 承诺范围内升级库版本时要回归验证。5.4 表格单元格文本设了不生效现象是给cell.text赋值后原本设置的加粗、字号全丢了。原因是cell.text的语义是「清空该单元格并写入新文本」重建了段落和 Run。解决办法是先拿cell.paragraphs[0].add_run(...)逐步写或者先赋值文本、再遍历cell.paragraphs里的 Run 补格式。顺序搞反就是白干。5.5 遍历不到表格里的文字现象是用doc.paragraphs数段落结果数量明显偏少模板里的表格内容一个都没拿到。原因是文档模型里表格是独立节点表格内的段落不属于doc.paragraphs。解决方法是显式遍历doc.tables再进rows → cells → paragraphs。如果文档里还有嵌套表格或文本框还需要继续往 XML 层挖这时建议先打印doc.element.xml看清实际结构再写逻辑。6. 进阶技巧与验证习惯把生成结果当代码一样对待到了能稳定生成文档的阶段真正拉开差距的不是 API 记得多而是有没有一套验证流程。我的习惯是凡是脚本生成的 docx一律先跑一遍结构断言再人工抽看。结构断言不做渲染只校验内容层能挡住绝大多数低级错误。from docx import Document def assert_doc(path, expect_keywords, min_tables0): doc Document(path) full_text \n.join(p.text for p in doc.paragraphs) for kw in expect_keywords: assert kw in full_text, f缺少关键内容: {kw} assert len(doc.tables) min_tables, f表格数量不足: {len(doc.tables)} # 空文档或占位符未替换的兜底检查 assert {{ not in full_text, 存在未替换的占位符 print(f校验通过: 段落 {len(doc.paragraphs)} 个, 表格 {len(doc.tables)} 个) assert_doc(filled.docx, [月度运行报告, 1280], min_tables1)这段断言的价值在于把「人工看一眼」变成可重复执行的检查。{{残留检查尤其值得加占位符漏替换在批量生成里非常隐蔽几十份文档里错一份靠人眼看根本发现不了。表格数量断言则能挡住模板变更导致的表格丢失。再往前一步可以把「样式核对」也纳入验证比如检查所有一级标题的字体是否统一from docx import Document from collections import Counter doc Document(report.docx) sizes Counter() for p in doc.paragraphs: if p.style.name.startswith(Heading): for run in p.runs: sizes[str(run.font.size)] 1 print(标题字号分布:, dict(sizes))如果分布里出现多种字号说明有地方漏设或继承混乱这在用模板改出来的文档里很常见。另一种值得掌握的技巧是批量处理时统一走「读—改—写」而不是「读—改—存回原文件」输出到新路径原模板永远保持干净一旦写坏了还能重来这就是给自己留后悔药。还有一个容易被忽略的点Document()不带参数时用的是库内置的默认模板很多自定义样式名在里面并不存在所以生产环境建议始终基于一份你们自己的模板文件Document(template.docx)打开再写内容样式名和页边距都跟着模板走比在代码里逐个设参数稳得多。我早期吃过亏代码里硬设了一堆字体和边距模板一换全乱改成基于模板后代码量少了一半。从那以后我每次生成批量文档都强制走一遍「结构断言 抽看一份 输出到新路径」这三步哪怕只生成两份也不省略。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑