如何使用Python修改DOCX、PDF文件字体样式以解决文本提取异常问题
Python修改DOCX、PDF文件字体的实现方案
针对DOCX格式文件的处理
处理DOCX不需要复杂的底层解析,用python-docx库就能实现,要避开的最大坑是——别只改文档的全局默认样式,很多DOCX里的文本会直接在独立的文本单元上覆盖字体设置,全局样式改了根本不生效。
- 依赖准备:直接安装库即可,命令为
pip install python-docx - 核心处理逻辑:
- 必须遍历文档所有层级的最小文本单元(也就是
run对象,DOCX里实际的字体配置就是存在这个单元上的),覆盖范围包括:正文段落、所有表格的单元格文本、页眉、页脚、脚注、文本框内的内容,漏了哪部分,哪部分的字体就不会被修改 - 对每个文本单元单独配置西文字体为Arial,同时要设置东亚字体属性,毕竟DOCX里西文和中文的字体是分开存储的,不单独配东亚字体,中文部分还是会保留原来的特殊字体
- 可直接复用的参考实现:
from docx import Document from docx.oxml.ns import qn def set_docx_font(input_path, target_english_font="Arial"): doc = Document(input_path) # 处理正文段落 for para in doc.paragraphs: for run in para.runs: run.font.name = target_english_font run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 处理所有表格文本 for table in doc.tables: for row in table.rows: for cell in row.cells: for para in cell.paragraphs: for run in para.runs: run.font.name = target_english_font run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 处理页眉页脚 for section in doc.sections: for header_para in section.header.paragraphs: for run in header_para.runs: run.font.name = target_english_font run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') for footer_para in section.footer.paragraphs: for run in footer_para.runs: run.font.name = target_english_font run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') doc.save("modified_output.docx") - 必须遍历文档所有层级的最小文本单元(也就是
- 踩坑提醒:逐单元修改完之后一定要抽查,有些文档里的超链接、批注里的文本不会被常规遍历覆盖到,可以单独补一层遍历逻辑处理这部分内容,漏了就还是会出现文本提取错位的问题。
针对PDF格式文件的处理
PDF是定版排版格式,和DOCX的流式编辑逻辑完全不同,没有统一的全局字体配置入口,别信网上那种直接改PDF样式属性换字体的教程,大部分非正规工具生成的PDF根本没有规范的字体对象结构,硬改只会把文件搞到打不开,目前Python生态里最稳定的实现思路是原文本重绘。
提前做个判断:如果原PDF是纯扫描件(图片格式,没有可提取的文本层),改字体没有意义,需要先做OCR识别生成正确的文本层再处理;如果是带文本层的可编辑PDF,按下面的逻辑走就行。
- 依赖准备:用PyMuPDF(安装包名
pymupdf)处理,安装命令:pip install pymupdf - 核心处理逻辑:
- 逐页提取原PDF的文本块内容、位置坐标、字号、颜色信息
- 用和页面背景同色的矩形把原来的旧文本层完全覆盖,把因为字体映射错位导致的错误字形彻底从页面上抹掉
- 在原来的文本位置,用Arial字体重新写入之前提取到的正确文本内容,对齐原排版参数,保证视觉上和原文档一致
- 可直接复用的参考实现:
import fitz def set_pdf_font(input_path, target_font="arial"): doc = fitz.open(input_path) # 加载内置的Arial字体文件 font_buffer = fitz.get_fontfile(target_font) for page in doc: # 提取页面所有带位置信息的文本块 text_blocks = page.get_text("dict")["blocks"] for block in text_blocks: if block["type"] != 0: # 跳过图片类块 continue for line in block["lines"]: for span in line["spans"]: # 用白色矩形覆盖原文本 text_rect = fitz.Rect(span["bbox"]) page.draw_rect(text_rect, color=(1,1,1), fill=(1,1,1)) # 重新加载Arial字体,在原位置写入文本 page.insert_font(fontname="custom_arial", fontbuffer=font_buffer) page.insert_text( point=(span["origin"][0], span["origin"][1]), text=span["text"], fontname="custom_arial", fontsize=span["size"], color=span["color"] ) doc.save("modified_output.pdf") - 踩坑提醒:重绘的时候要注意对齐原文本的基线坐标,不然会出现文字位置偏移,视觉效果变差。
效果验证环节
改完字体之后不要直接批量处理全量文件,先拿之前提取出问题的样本做验证:
- 用你之前做文本提取的同款逻辑跑改后的文件,重点检查之前错位的字符(比如数字0被识别成@、特殊符号错配的问题)是否修正
- 验证的时候要覆盖数字、英文、特殊符号段,不要只测试中文文本,不然很容易漏掉错配的部分
内容的提问来源于stack exchange,提问作者Aditya
相关产品推荐
相关产品推荐

