You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

提取带格式Word文档内容:python-docx获取字体信息返回None问题

提取Word文档格式信息的问题与解决方案

问题背景

我需要提取Word文档中包含字体、字号、图片、批注等元素的格式信息,已经用zipfile模块将文档解压为XML文件,得到的文件列表如下:

['[Content_Types].xml', '_rels/.rels', 'word/_rels/document.xml.rels', 'word/document.xml', 'word/footer2.xml', 'word/header1.xml', 'word/footer1.xml', 'word/endnotes.xml', 'word/footnotes.xml', 'word/_rels/header1.xml.rels', 'word/header2.xml', 'word/_rels/header2.xml.rels', 'word/embeddings/Microsoft_Word_97_-_2003_Document1.doc', 'word/media/image3.wmf', 'word/media/image2.emf', 'word/theme/theme1.xml', 'word/media/image1.png', 'word/embeddings/oleObject1.bin', 'word/comments.xml', 'word/settings.xml', 'word/styles.xml', 'customXml/itemProps1.xml', 'word/numbering.xml', 'customXml/_rels/item1.xml.rels', 'customXml/item1.xml', 'docProps/app.xml', 'word/stylesWithEffects.xml', 'word/webSettings.xml', 'word/fontTable.xml', 'docProps/core.xml', 'docProps/custom.xml']

但我搞不懂word/document.xml里的内容和样式是如何关联的,最终希望把提取结果封装成这样的字典结构:

{ 
    "text": "some-text-in-document", 
    "font": "some-font", 
    "font_size": 10, 
    "some_field": "some-more-value", 
    ... 
}

我尝试用python-docx获取字体和字号,但返回值大多是None,代码片段如下:

from docx.enum.style import WD_STYLE_TYPE
styles = document.styles
#print(styles.default)
paragraph_styles = [s for s in styles if s.type == WD_STYLE_TYPE.PARAGRAPH]
for style in paragraph_styles:
    #print(style.font.name)
    if(style.font.name):
        print(style.font.name, style.font.size)
for paragraph in document.paragraphs:
    #print(paragraph.text)
    for run in paragraph.runs:
        print(run.text)
        font = run.style.font
        print(font.size)

解决思路与方案

1. 修正python-docx代码,正确获取字体信息

你得到None的核心原因是:Word的格式分直接应用到Run的字符格式通过样式继承的格式,之前的代码只读取了run.style.font(样式里的格式),但很多时候格式是直接设置在Run本身的。另外,字号返回的是Pt对象,需要转换为数值。

调整后的代码可以同时获取直接格式和样式继承的格式:

from docx import Document

def extract_run_format(run):
    # 优先取直接应用的格式,没有则 fallback 到样式中的设置
    font_name = run.font.name or run.style.font.name
    # 转换字号为磅值(docx返回的Pt对象需要取.pt属性)
    font_size = run.font.size.pt if run.font.size else (run.style.font.size.pt if run.style.font.size else None)
    # 可扩展提取粗体、斜体等其他格式
    return {
        "text": run.text.strip(),
        "font": font_name,
        "font_size": font_size,
        "bold": run.font.bold or run.style.font.bold,
        "italic": run.font.italic or run.style.font.italic
    }

# 替换成你的文档路径
doc = Document("your_document.docx")
formatted_data = []

# 遍历所有段落和Run,过滤空文本
for para in doc.paragraphs:
    for run in para.runs:
        if run.text.strip():
            formatted_data.append(extract_run_format(run))

# 输出提取结果
for item in formatted_data:
    print(item)

2. 直接解析XML文件,关联样式与内容

如果要深入处理XML文件,需要结合word/styles.xmlword/document.xml:前者定义了所有样式的属性,后者的文本节点通过styleId引用样式。这里用lxml库来解析:

from lxml import etree

# 定义Word的XML命名空间
NS = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}

# 第一步:解析styles.xml,建立样式ID到属性的映射
style_tree = etree.parse("word/styles.xml")
style_map = {}
for style in style_tree.xpath("//w:style", namespaces=NS):
    style_id = style.get(f"{{{NS['w']}}}styleId")
    # 提取字体名称
    font_name_node = style.xpath(".//w:name", namespaces=NS)
    font_name = font_name_node[0].get(f"{{{NS['w']}}}val") if font_name_node else None
    # 提取字号(Word用半磅为单位,需除以2得到实际磅值)
    font_size_node = style.xpath(".//w:sz/@w:val", namespaces=NS)
    font_size = int(font_size_node[0])/2 if font_size_node else None
    style_map[style_id] = {"font": font_name, "font_size": font_size}

# 第二步:解析document.xml,提取文本并匹配样式
doc_tree = etree.parse("word/document.xml")
extracted_items = []
for run in doc_tree.xpath("//w:r", namespaces=NS):
    # 提取Run中的文本
    text = "".join(run.xpath(".//w:t/text()", namespaces=NS)).strip()
    if not text:
        continue
    # 获取Run引用的样式ID
    style_id_node = run.xpath(".//w:rStyle/@w:val", namespaces=NS)
    style_info = style_map.get(style_id_node[0]) if style_id_node else {}
    # 检查直接应用的格式(优先级高于样式)
    direct_font = run.xpath(".//w:rFonts/@w:ascii", namespaces=NS)
    direct_size = run.xpath(".//w:sz/@w:val", namespaces=NS)
    # 组装结果
    item = {
        "text": text,
        "font": direct_font[0] if direct_font else style_info.get("font"),
        "font_size": int(direct_size[0])/2 if direct_size else style_info.get("font_size")
    }
    extracted_items.append(item)

# 输出结果
for item in extracted_items:
    print(item)

3. 扩展处理图片、批注等元素

  • 图片:查看word/_rels/document.xml.rels,里面记录了图片的关联路径(对应word/media/下的文件),再解析document.xml中的w:drawing节点,可以获取图片在文档中的位置信息。
  • 批注:直接解析word/comments.xml,里面包含批注的文本、作者和关联的段落ID,通过ID可以和document.xml中的内容对应起来。

内容的提问来源于stack exchange,提问作者Asif Ali

火山引擎 最新活动