周一早上九点,你打开电脑,发现领导昨晚十一点发来的消息:"小张,帮我把这份合同里所有的公司名称改成加粗红色,五十页那个,今天开会要用。"
你打开文档,翻到第一页,选中公司名,点加粗,点红色,然后滚到下一页。五十页的文档,光滚动就花了五分钟。你揉了揉眼睛,心想这要是每个月都来这么一次,眼睛迟早要废。
这种机械化的Word排版工作,最适合交给Python。python-docx这个库,就是专门干这个的。
先搞清楚Word文档的"三层结构"
在用python-docx之前,得先理解一个概念:Word文档的文本不是一整块,而是分了三层。
最外层是Document ,代表整个文档。往下一层是Paragraph ,也就是段落。再往下是Run,这才是真正存放文字的地方。
为什么要有Run这个概念?因为一个段落里可能有好几种不同的格式。比如"这是加粗的文字"这句话,实际上被分成了三个Run:第一个Run是"这是",第二个Run是"加粗"(带加粗属性),第三个Run是"的文字"。每个Run可以单独设置字体、大小、颜色。
理解这个结构,后面的所有操作就好办了------想改格式,就要找到对应的Run,而不是直接改段落。
环境准备
先装库:
pip install python-docx
在代码里导入的时候,用docx这个名字,不是python-docx:
javascript
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
shared里面是尺寸和颜色相关的工具,enum.text里是对齐方式之类的常量。
读取现有文档
假设你要处理的是那份五十页的合同:
ini
doc = Document('合同.docx')
这样就加载进来了。doc.paragraphs里放着所有段落,你可以遍历它们:
arduino
for para in doc.paragraphs:
print(para.text)
但只打印文本不够,你要改的是格式。格式藏在每个段落的runs里面:
yaml
for para in doc.paragraphs:
for run in para.runs:
# 在这里操作每个run
改字体:加粗、颜色、大小
回到那个需求:把所有"某某科技有限公司"改成加粗红色。
ini
from docx import Document
from docx.shared import Pt, RGBColor
doc = Document('合同.docx')
target = '某某科技有限公司'
for para in doc.paragraphs:
for run in para.runs:
if target in run.text:
# 把这个run里的目标文字单独拎出来改格式
# 注意:如果run里不止目标文字,需要拆开处理
run.font.bold = True
run.font.color.rgb = RGBColor(255, 0, 0) # 红色
run.font.size = Pt(12) # 字号,可选
但这里有个坑。如果一个run里是"本合同的乙方为某某科技有限公司,特此声明",你直接把整个run加粗标红,那整句话都变了。你只想要"某某科技有限公司"这个关键词变色。
解决办法是把run拆开:
ini
for para in doc.paragraphs:
for run in para.runs:
if target in run.text:
# 获取run的原始格式
original_text = run.text
# 按目标关键词拆分
parts = original_text.split(target)
# 清空当前run
run.text = parts[0]
# 插入目标关键词,带新格式
new_run = para.add_run(target)
new_run.font.bold = True
new_run.font.color.rgb = RGBColor(255, 0, 0)
# 插入剩下的文本,保留原格式
if len(parts) > 1:
remaining_run = para.add_run(parts[1])
# 复制原格式
remaining_run.font.size = run.font.size
remaining_run.font.name = run.font.name
这个逻辑稍微有点绕,但核心思路就是:找到关键词,把run拆成三段,中间那段单独设格式。
改段落:对齐、行距、缩进
有时候你需要改的不是某个词,而是整个段落的样式。比如把某些段落居中对齐。
arduino
from docx.enum.text import WD_ALIGN_PARAGRAPH
for para in doc.paragraphs:
if '重要提示' in para.text:
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
行距和段间距也可以调。paragraph_format这个属性管这些:
ini
from docx.shared import Pt
for para in doc.paragraphs:
paragraph_format = para.paragraph_format
paragraph_format.line_spacing = Pt(20) # 行距20磅
paragraph_format.space_before = Pt(12) # 段前距
paragraph_format.space_after = Pt(6) # 段后距
首行缩进是中文文档的常见需求:
arduino
from docx.shared import Pt
for para in doc.paragraphs:
# 排除标题等不需要缩进的段落
if len(para.text) > 10 and not para.text.startswith('一、'):
para.paragraph_format.first_line_indent = Pt(24) # 两个字符
改标题样式
文档里的标题通常有自己的样式。python-docx可以直接通过样式名来设置:
ini
# 添加一个新标题
doc.add_heading('第一章 概述', level=1)
但如果要改已有的标题样式,可以这样:
ini
for para in doc.paragraphs:
if para.style.name.startswith('Heading'):
# 这是标题段落
for run in para.runs:
run.font.bold = True
run.font.size = Pt(16)
中文字体的问题
默认情况下,设置run.font.name = '宋体'可能不生效。因为Word里的中文字体需要用另一个属性指定:
arduino
from docx.oxml.ns import qn
run = para.add_run('这是一段中文')
run.font.name = '宋体'
run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
w:eastAsia就是指定东亚字体的那个属性。这个写法有点绕,但好在可以封装成一个函数:
arduino
def set_chinese_font(run, font_name):
run.font.name = font_name
run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
批量处理多个文档
如果不止一份合同要改,而是整个文件夹的文档都需要统一格式:
ini
import os
from docx import Document
folder = '待处理文档'
output_folder = '处理后文档'
for filename in os.listdir(folder):
if filename.endswith('.docx'):
filepath = os.path.join(folder, filename)
doc = Document(filepath)
# 在这里执行你的格式化操作
for para in doc.paragraphs:
for run in para.runs:
# 你的逻辑
pass
# 另存,避免覆盖原文件
output_path = os.path.join(output_folder, filename)
doc.save(output_path)
保存
改完之后,记得保存:
arduino
doc.save('合同_已格式化.docx')
建议另存为新文件,万一改错了还能回去找原版。
回到那个周一早上的场景。如果你用上面的代码跑一遍,五十页的合同大概只需要两秒钟。加粗、标红、调字号、改标题样式,一气呵成。剩下的时间,你可以安心喝杯咖啡,而不是在文档里反复滚动鼠标。
python-docx不能帮你写报告,但至少能让你不用再做那些重复的点鼠标动作。这大概是它最值钱的地方。