资讯动态

Python批量统一Word格式:30秒处理100份合同的自动化脚本实战

发布时间:2026/10/16 13:26:26 来源:尧图企业网站定制
先交代一下背景。前段时间公司里积了一百多份历史合同文档格式乱得五花八门有的标题用了黑体三号有的用宋体四号加粗行距有的是单倍有的是固定值20磅页边距更是各玩各的。上级要求全部统一成同一套排版规范再归档手动改的话按小时算都打不住。我花了几十分钟写了个Python脚本实测跑完这一百多份Word只用了不到30秒几百行代码都没有核心就几十行但用起来是真的香。这篇文章我把批量处理Word格式调整的完整思路、可运行脚本和踩过的坑都写清楚适合所有需要批量整理docx的办公场景也适合刚学Python想找实际练手项目的朋友照着抄。1. 痛点与整体思路为什么这件事值得自动化1.1 手动调整格式的真实痛点先说一个大家都有过的经历打开第一份Word格式不对全选改字体改字号调行距调缩进然后关掉。第二份再来一遍。第一份文件人还比较清醒改到第五十份的时候已经开始烦躁改到第八十份的时候大概率会漏掉某个标题或者段落。这跟技术没关系纯粹是重复劳动必然带来的注意力衰退。而且这些文件往往来自不同的人、不同的模板格式不统一几乎是必然的。有的文档标题用样式有的文档标题就是普通文字加粗有的人正文用的是宋体小四有的人正文是仿宋五号还有一些文件是从旧版本转出来的行距设置被压得密密麻麻。手动操作面对这种混合情况光是“检查哪里不对”这个动作就已经很耗时了更不要说逐项修正。换个角度看这类任务的本质就是规则明确、重复度高、操作机械。它和“把一百个文件名统一改成前缀加编号”“把一百张图片压缩到统一尺寸”是同一类问题。这一类问题正是脚本和自动化工具最适合发挥的场景。人力做这种任务省下时间去做需要判断力的事情才是正途。1.2 自动化方案的三个关键步骤把批量格式调整拆开看其实就三步找到所有需要处理的文件、打开并解析文档内容、按统一规则重新设置格式并保存。第一步“找到文件”只需要用pathlib递归遍历目录匹配.docx后缀同时注意跳过Word临时文件~$开头的文件。第二步“解析文档”在Python生态里最顺手的库就是python-docx它的对象模型和Word文档本身的结构是对应的所有段落、样式、节section都能直接访问和修改。第三步“设置格式”就是把字体、字号、行距、缩进、页边距这些参数统一赋值这部分其实就是写一遍格式规则。这三步单独拎出来都不复杂组合在一起就是一个能反复使用的工具。这次跑完合同下次遇到格式乱的标书、论文、会议纪要改一下规则参数就能再用。脚本的价值在于把一次性劳动变成可复用的资产。1.3 这脚本到底能帮你解决什么从实际操作者的角度看这个脚本解决的不是“把这一百份文档改完”这一次性的问题而是彻底改变处理格式问题的方式。以前拿到一堆格式混乱的文档第一反应是叹气现在第一反应是“先看有多少份、规则是什么然后写脚本”。心态完全不同。适合使用这个方案的场景包括整理合同归档模板、批量格式化投标文件、招生季处理报名表、毕业生论文格式统一处理、历史资料数字化后的版面清理等。只要有明确格式要求、文件数量超过二三十份、规则不需要复杂逻辑判断就可以直接用。2. 环境准备与核心概念先弄懂原理再动手2.1 环境安装与版本要求这个脚本依赖的库只有一个就是python-docx。安装命令很简单pip install python-docx如果是在国内网络环境可以指定镜像源安装速度更快pip install python-docx -i https://pypi.tuna.tsinghua.edu.cn/simplePython版本建议3.8以上太低版本的语法兼容性会有些小问题。python-docx目前只支持.docx格式的文档不支持老的.doc格式。如果手里是.doc文件需要先用WPS或Office打开另存为.docx或者用LibreOffice做一次批量转换然后再用脚本处理。注意批量操作前建议先把文件放到一个单独的文件夹里做一次测试运行确认输出没有问题再到正式目录上跑。这个习惯能避免很多“改完全部才发现字体方向搞错了”的返工。2.2 python-docx的对象模型是怎么回事刚开始接触python-docx的人最先容易绕晕的就是它的一堆对象关系。我用一句话类比一份Word文档就像一栋毛坯房Document对象是整个房子section相当于房间的墙体边界paragraph是房间里一个个空间run则是空间里一块块具体的家具摆设。更技术一点说一个Paragraph段落里可以包含多个Run每个Run是一段具有相同格式的文本片段。Word里你选中一段文字改颜色其实就是编辑这个段落的某个Run。所以脚本里要精确控制某段文字的格式通常需要遍历para.runs逐个设置run.font而段落级别的格式行距、缩进、对齐则直接改paragraph_format。另外还有一个容易忽略的对象是Style样式。如果文档本身用了“标题 1”“正文”等样式那么直接修改对应样式的字体、字号全文所有使用该样式的段落会同步变化效率比逐段跑run高很多。但现实里的文档经常不按样式规范来很多“标题”就是普通段落加粗、放大字号所以脚本里最好还是段落级和run级双管齐下保证能接住各种混乱情况。2.3 中文字体不生效关键在eastAsia属性这是新手最常见的坑。用python-docx给run设置字体的第一反应一般是run.font.name 宋体代码倒是不报错但保存后打开文档中文字体纹丝不动。原因是Word内部把字体分为“西文字体”和“东亚文字字体”两套体系run.font.name只设置西文字体没动中文字体。正确的做法是在XML层面对w:eastAsia属性进行设置from docx.oxml.ns import qn run.font.name Times New Roman r run._element.rPr.rFonts r.set(qn(w:eastAsia), 宋体)这背后的原理是每个run的字体属性在docx文件中对应一个rPr节点里面可以同时指定w:ascii、w:hAnsi、w:eastAsia等字体分类。我习惯把它封装成一个函数不然后面手动写容易漏。3. 核心脚本实现从需求到可运行代码3.1 先明确需求把100份文档统一成什么格式写代码之前第一件事是把格式规范定清楚。我这里以比较常见的公文/合同排版要求为例项目格式要求正文字体中文字体宋体西文字体Times New Roman正文字号小四12pt一级标题黑体三号16pt加粗二级标题黑体四号14pt加粗三级标题黑体小四12pt加粗正文行距1.5倍行距首行缩进2字符约0.85cm页面边距上下2.54cm左右3.17cm不同单位、不同项目的格式要求会有差异但核心参数就是这几样。把这些定义成脚本顶部的常量后面改需求时只需要改一处。3.2 完整脚本遍历、修改、保存下面是我实际在用的脚本简化版直接可运行。例子里的输入目录设置为./docs输出直接覆盖原文件但加了备份逻辑。from pathlib import Path from docx import Document from docx.shared import Pt, Cm from docx.oxml.ns import qn from docx.enum.text import WD_ALIGN_PARAGRAPH INPUT_DIR Path(./docs) BACKUP_DIR Path(./docs_backup) # 格式规则 CN_FONT 宋体 EN_FONT Times New Roman BODY_SIZE Pt(12) H1_SIZE Pt(16) H2_SIZE Pt(14) H3_SIZE Pt(12) LINE_SPACING 1.5 def set_run_font(run, cn_font, en_font, size, boldFalse): 一次性设置中英文格式的run run.font.name en_font r run._element.get_or_add_rPr() rPr run._element.rPr rFonts rPr.get_or_add_rFonts() rFonts.set(qn(w:eastAsia), cn_font) run.font.size size run.font.bold bold def process_paragraph(para): 处理单个段落的字体和段落格式 # 如果段落为空没有 runs直接跳过 if not para.runs: return # 判断标题等级优先使用样式名 style_name para.style.name if Heading 1 in style_name or 标题 1 in style_name: for run in para.runs: set_run_font(run, 黑体, EN_FONT, H1_SIZE, boldTrue) elif Heading 2 in style_name or 标题 2 in style_name: for run in para.runs: set_run_font(run, 黑体, EN_FONT, H2_SIZE, boldTrue) elif Heading 3 in style_name or 标题 3 in style_name: for run in para.runs: set_run_font(run, 黑体, EN_FONT, H3_SIZE, boldTrue) else: # 正文统一处理 for run in para.runs: set_run_font(run, CN_FONT, EN_FONT, BODY_SIZE, boldFalse) # 段落格式 pf para.paragraph_format pf.line_spacing LINE_SPACING pf.space_before Pt(0) pf.space_after Pt(0) # 正文设置首行缩进标题不缩进 if not (Heading in style_name or 标题 in style_name): pf.first_line_indent Cm(0.85) def process_document(doc_path): doc Document(str(doc_path)) # 处理所有段落 for para in doc.paragraphs: process_paragraph(para) # 处理表格中的段落容易被忽略 for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: process_paragraph(para) # 设置页边距 for section in doc.sections: section.top_margin Cm(2.54) section.bottom_margin Cm(2.54) section.left_margin Cm(3.17) section.right_margin Cm(3.17) doc.save(str(doc_path)) def main(): # 先备份 BACKUP_DIR.mkdir(exist_okTrue) doc_files list(INPUT_DIR.rglob(*.docx)) for doc_path in doc_files: # 跳过Word临时文件 if doc_path.name.startswith(~$): continue # 备份原文件 rel_path doc_path.relative_to(INPUT_DIR) backup_path BACKUP_DIR / rel_path backup_path.parent.mkdir(parentsTrue, exist_okTrue) backup_path.write_bytes(doc_path.read_bytes()) # 处理文档 process_document(doc_path) print(f已处理: {doc_path}) print(f全部完成共处理 {len(doc_files)} 个文件) if __name__ __main__: main()这段代码的核心逻辑分三块备份原文件、逐文件处理、处理时依次覆盖“段落、表格、页边距”。第一次跑完建议随机抽查几份文档看一下标题级别有没有被正确识别正文缩进是否统一。3.3 代码关键点拆解为什么这么写先看set_run_font这个函数它接收的角度是“一个run 字体 字号 加粗”所有格式设置都在这个函数里完成。它的核心作用就是解决上一节提到中文字体不生效的问题每次设置字体都会同时带上w:eastAsia。这个函数可以复制到任何脚本里复用。再看process_paragraph函数它依据段落样式名来判断标题级别这是方案选型里的一个取舍点。如果文档里的标题用的都是内建Heading样式这种判断准确率非常高如果设置混乱那就只能靠文本特征比如“第X章”“一、”等开头来识别复杂度会明显上升。我在实际处理合同的时候发现大部分文档标题还是规范用了样式的所以先用样式名判断命中率已经足够高。表格处理是我特别加上的。很多格式整理脚本只处理doc.paragraphs但实际情况里合同里经常有附件表格表格里也是正文内容。如果不管表格就会出现“页面正文全部统一了表格里还是老字体”的半吊子结果。这一段遍历表格的代码把表格里的每个单元格里的段落也过一遍确保全文档覆盖。提示get_or_add_rPr()和get_or_add_rFonts()是为了防止run还没有字体属性节点时报错属于防御式写法。如果你确定文档一定有字体设置直接用run._element.rPr.rFonts也可以但通用性不如get_or_add写法。3.4 性能分析为什么30秒能跑完100份一百份文档30秒跑完每份平均0.3秒。这个速度其实不靠什么高深技术因为对普通docx文件来说瓶颈主要在硬盘读写IO而不是CPU。普通合同正文也就几千到几万字解析和重写都是纯内存操作Word打开一份文件可能都要两三秒脚本处理比人用Word操作快得多因为脚本不会去渲染界面、加载字体、计算排版只做结构数据读写。真正影响速度的因素有两个文件大小和磁盘类型。如果是几百页带大量图片的文档体量几百MB那单份处理时间会明显上升如果源文件在机械硬盘上读取速度也会慢一些。要想再提速可以用多进程并行处理把文件列表分给几个进程同时跑但在100份普通合同这个量级上30秒已经足够体面我实际没有引入多进程原因是没必要增加复杂度。脚本的一次性运行成本完全可以接受。4. 实操中遇到的坑与排查技巧实录4.1 中文字体死活不生效这个问题在2.3已经详细说过原因这里再补充一个表现同一段文字设置完字体后打开Word看中文显示宋体但数字和英文还是Calibri。其实这是正常的因为w:eastAsia只管中文西文归w:ascii管。如果希望所有字符都用同一种字体两个属性都要设置成同一个字体。我这里刻意把中西文分开设置是为了符合“中文宋体英文Times New Roman”的公文排版习惯这个度自己掌握。4.2 表格里的文字没有被处理到如果脚本只循环了doc.paragraphs那表格里的内容一定不会被处理。第一次写完脚本跑完我抽查了一份文档才发现这个问题表格里的条款还保持着旧字体和旧缩进。解决办法就是遍历doc.tables的每个cell.paragraphs把表格内段落也交给同一个处理函数。另外还要注意嵌套表格某些复杂排版里表格里面还有表格只循环外层表格会把嵌套表格漏掉稳妥的做法是用递归遍历table.tables。4.3 空段落和空run引发的报错有些段落没有任何内容para.runs是空列表循环不到任何run这本身不会报错。但如果在process_paragraph里一开始就取para.runs[0]就会报IndexError。我在代码里已经用if not para.runs: return做了保护。还有一类情况是段落里全是图片或嵌入对象runs为空跳过是对的不要硬改。这种段落在格式整顿时保留原样通常不影响整体效果。4.4 文件被Word占用、权限不足导致保存失败如果待处理文档正好在Word中打开着脚本运行到doc.save()时会报PermissionError因为文件被锁定。我的处理办法是在处理前先检查一下目标文件是否被占用可以在文件系统层面尝试以追加方式打开文件打不开就跳过并记录或者干脆在跑脚本前关掉所有Word窗口。还有一种是文件权限只读保存也会失败。把文件先复制出来改完再复制回去也是一种妥协方案但备份逻辑本身就要做所以两者可以结合。4.5 常见报错与解决速查表报错现象可能原因处理办法中文字体没变只设置了font.name没设置w:eastAsia用qn(w:eastAsia)设置中文字体IndexError: list index out of range空段落没有runs先判断if not para.runs再处理PermissionError文档正被Word占用关闭所有Word窗口后重试FileNotFoundError输入目录路径不对检查INPUT_DIR路径是否真实存在表格内容未统一只处理了doc.paragraphs增加表格遍历逻辑保存格式异常文档本身结构损坏先手动打开确认文档可正常编辑再跑脚本脚本跑完没有变化样式名与实际不匹配打印para.style.name确认再调整判断逻辑4.6 处理前的备份习惯分享一个我自己体会很深的习惯批处理操作备份永远优先。这个脚本里我把原文件备份到了docs_backup目录按相对路径保持目录结构这样即使脚本跑了之后发现效果不对也能随时恢复。在实际项目里我还养成了“先跑1-2份人工确认再全量跑”的方式这个流程看着多花几分钟实际上能避免大范围返工。你永远不想知道“批量处理完100份才发现缩进参数设错了”是什么感受。5. 再往前一步这个脚本还能怎么扩展5.1 批量替换文本和关键词高亮格式调整只是批量处理Word的第一步实际场景里“把文档里的旧公司名统一替换成新公司名”“把某些关键词标红高亮”也非常常见。在python-docx里文本替换比想象中麻烦一点因为文本是分散在多个run里的一个词可能被拆成几个run存储。简单场景下可以逐段拼接段落全文找到目标词后在匹配处重建run并设置高亮复杂场景下建议直接用Word自带的“查找替换”功能脚本适合处理规则更明确的重复任务文本替换这种带着不确定性的人工作业我不太推荐强上脚本。5.2 依据文件名套用不同格式模板还有一种很实用的扩展根据文件名关键词套不同的格式模板。比如文件名含“合同”的走合同格式含“报告”的走报告格式。这只需要在main()里判断一下文件名然后调用不同的格式规则函数即可。好处是用一套脚本管多种文档类型不用每种类型维护一个脚本。坏处是规则多了之后脚本会变得复杂建议把格式规则抽象成字典或单独配置模块保持可维护性。5.3 输出处理结果报告跑完100份文件哪些处理成功、哪些失败、哪些跳过了如果只看print输出很容易漏掉。扩展思路是把处理结果写到一份CSV报告里包含文件名、处理状态、处理耗时、备注。这样即使中途有文件出错也能一目了然知道哪份需要人工处理。报告本身也是项目留痕的一部分格式整理类项目最终归档时附上一份“处理记录”是非常加分的。6. 关于这个脚本我最想说的几件事写了一整天脚本真正跑完那100份文档的时候我的第一感觉不是“省了多少时间”而是“这个技能以后会一直用下去”。因为Word格式调整和文件批处理这种需求永远不会消失每来一批新文档脚本改几个参数就能复用。对非程序员背景的朋友来说写这样一个脚本的难度并不高真正难的是遇到问题时知道怎么排查。这篇文章里写的坑全都是我实际踩过的把这些问题提前告诉你至少能让你少走几小时弯路。最后分享一个具体操作建议把这个脚本保存成一个batch_format_docx.py文件放在固定的工具目录里同时写一个简单的README.txt记录参数含义。这样半年后你再遇到格式统一任务不需要重新回忆代码逻辑看一眼说明就能直接上手。批量处理这种事脚本本身只是手段真正的价值是让重复劳动变得不再可怕。

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

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

免费获取报价 →
↑