python如何解析word文件格式(.docx)

python如何解析word文件格式(.docx)

.docx文件遵从开源的“Office Open XML标准”,这意味着我们能用python的文本操作对它进行操作(实际上PPT和Excel也是)。而且这并不是重复造轮子,因为市面上操作.docx的库限制性非常强:

  • python-docx是开源的,但不支持高级操作,例如增加批注、修订等。
  • spire.doc支持高级操作,但需要商业许可
  • 微软开放了word的api接口,但不是跨平台的。只支持win平台,且学习门槛较高

所以我们从开源的标准入手,用python实现操作word文件的功能。


看下图,把.docx文件的后缀手动改为.zip竟然可以直接解压。原来.docx本质上是一个zip压缩包。

请添加图片描述

解压后的word文件漏出了他的真实面目。原来.docx由很多.xml文件(及其他)组成。注意下图框出的word/document.xml,他是我们操作word文件的主角(有些高级功能不在其中,比如批注在另外的xml中)。因为其中记录了word文档的文本、字体、段落格式等。.xml是一个纯文本文件,理论上我们用python可以操作word/document.xml中定义的任何元素。

请添加图片描述

解释一下.xml格式:你可以粗略的把它理解为.html。区别在于.html的标记是预先定义好的,.xml的标记由架构或文档的作者定义,并且是无限制的。如果你没有接触过的话,我建议百度一下,有助于理解下面的内容。

我举个例子来看一下word/document.xml的结构,具体的含义写在注释里了:

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">  # 注意这行,下文要用到!
    <w:body>  # body是文档的主体,是个nodelist,一般只有一个元素
        <w:p w:rsidR="00F921A6" w:rsidRDefault="00000000">  # p代表paragraph段落
            <w:pPr>  # pPr是段落属性
                <w:numPr>
                    <w:ilvl w:val="255"/>
                    <w:numId w:val="0"/>
                </w:numPr>
                <w:pBdr>  # 段落边框
                    <w:top w:val="none" w:sz="0" w:space="0" w:color="000000"/>
                    <w:left w:val="none" w:sz="0" w:space="0" w:color="000000"/>
                    <w:bottom w:val="none" w:sz="0" w:space="7" w:color="000000"/>
                    <w:right w:val="none" w:sz="0" w:space="0" w:color="000000"/>
                </w:pBdr>
                <w:widowControl/>  # 控制孤行
                <w:spacing w:line="560" w:lineRule="exact"/>  # 行间距
                <w:ind w:firstLineChars="200" w:firstLine="640"/>  # 首行缩进
                <w:outlineLvl w:val="1"/>  # 标题级别
                <w:rPr>  # rPr是段落内的文本属性
                    <w:rFonts w:ascii="方正仿宋_GBK" w:eastAsia="方正仿宋_GBK" w:hAnsi="Times New Roman"/>
                    <w:kern w:val="0"/>
                    <w:sz w:val="32"/>
                    <w:szCs w:val="32"/>
                </w:rPr>
            </w:pPr>
            <w:r>  # r代表run,可以理解为连续的文本块
                <w:rPr>  # rPr是文本属性
                    <w:rFonts w:ascii="方正楷体_GBK" w:eastAsia="方正楷体_GBK" w:hAnsi="Times New Roman" w:hint="eastAsia"/>  # 字体
                    <w:kern w:val="0"/>  # 字间距
                    <w:sz w:val="32"/>  # 字号
                    <w:szCs w:val="32"/>  # 字号?不知道
                </w:rPr>
                <w:t>这是一段word中的文本</w:t>  # t是文本
            </w:r>
        </w:p>
    </w:body>
</w:document>

注意看上面xml的第一行,xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"定义了document及其子元素的命名空间。xmlns用来声明属性,比如这里xmlns:w代表绑定了w为前缀,那么子元素中所有前缀为w的都绑定到命名空间"http://schemas.openxmlformats.org/wordprocessingml/2006/main"为什么要着重讲命名空间呢?因为docx的前缀不是统一的,比如Microsoft Word一般用w,但是wps就不用w了,记得在操作之前先确定命名空间。


以上只是举个列子,实际上各种标记种类和用法非常多。具体的标记用法参考OOXML标准,你可以在下图画红框的地方查找(仅适用于Microsoft Word)。

OOXML标准的中文翻译:https://hellowac.github.io/ecma-376-zh-cn/

请添加图片描述


python-docx对word的基础操作支持的不错,所以我们只需要自己开发高级功能部分,如批注、修订等。并且我建议大家可以在 python-docx的基础上开发,这样省时省力。
下面是批注功能的实现,其他功能的开发也大同小异。

# 代码来源于网络,我稍做修改并添加注释。
from datetime import datetime
from typing import List
from xml.etree.ElementTree import Element, tostring

from docx import Document
from docx.opc.part import Part
from docx.opc.constants import RELATIONSHIP_TYPE, CONTENT_TYPE
from docx.opc.oxml import parse_xml
from docx.opc.packuri import PackURI
from docx.oxml import OxmlElement
from docx.oxml.ns import qn

# 命名空间
_COMMENTS_PART_DEFAULT_XML_BYTES = (
    b"""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r
<w:comments
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:o="urn:schemas-microsoft-com:office:office"
    xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
    xmlns:v="urn:schemas-microsoft-com:vml"
    xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
    xmlns:w10="urn:schemas-microsoft-com:office:word"
    xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
    xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
    xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
    xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"
    xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart"
    xmlns:lc="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"
    xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram"
    xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
    xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
    xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
    xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
    xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml"
    xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex"
    xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"
    xmlns:cr="http://schemas.microsoft.com/office/comments/2020/reactions">
</w:comments>
"""
).strip()


def add_comment_to_elements_in_place(
    docx_doc: Document, 
    elements: List[Element], 
    comment_text: str, 
    author: str="AI助手",
) -> None:
    '''给段落添加批注'''
    if not elements:
        return
        
	# 尝试获取已有的批注,如果不存在的话就新建一个comments.xml
    try:
        comments_part = docx_doc.part.part_related_by(
            RELATIONSHIP_TYPE.COMMENTS
        )
    except KeyError:
        comments_part = Part(
            partname=PackURI("/word/comments.xml"),
            content_type=CONTENT_TYPE.WML_COMMENTS,
            blob=_COMMENTS_PART_DEFAULT_XML_BYTES,
            package=docx_doc.part.package,
        )
        docx_doc.part.relate_to(comments_part, RELATIONSHIP_TYPE.COMMENTS)

    comments_xml = parse_xml(comments_part.blob)
    
    # 创建批注信息(id/author/date)
    comment_id = str(len(comments_xml.findall(qn("w:comment"))))
    comment_element = OxmlElement("w:comment")
    comment_element.set(qn("w:id"), comment_id)
    comment_element.set(qn("w:author"), author)
    comment_element.set(qn("w:date"), datetime.now().isoformat())

    # 创建批注文本元素
    comment_paragraph = OxmlElement("w:p")
    comment_run = OxmlElement("w:r")
    comment_text_element = OxmlElement("w:t")
    comment_text_element.text = comment_text
    comment_run.append(comment_text_element)
    comment_paragraph.append(comment_run)
    comment_element.append(comment_paragraph)

    comments_xml.append(comment_element)
    comments_part._blob = tostring(comments_xml)

    # 创建批注范围开始和结束元素,分别放在开头和结尾
    comment_range_start = OxmlElement("w:commentRangeStart")
    comment_range_start.set(qn("w:id"), comment_id)
    comment_range_end = OxmlElement("w:commentRangeEnd")
    comment_range_end.set(qn("w:id"), comment_id)
    elements[0].insert(0, comment_range_start)
    elements[-1].append(comment_range_end)

    # 为每个元素添加批注引用
    comment_reference = OxmlElement("w:r")
    comment_reference_run = OxmlElement("w:r")
    comment_reference_run_properties = OxmlElement("w:rPr")
    comment_reference_run_properties.append(
        OxmlElement("w:rStyle", {qn("w:val"): "CommentReference"})
    )
    comment_reference_run.append(comment_reference_run_properties)
    comment_reference_element = OxmlElement("w:commentReference")
    comment_reference_element.set(qn("w:id"), comment_id)
    comment_reference_run.append(comment_reference_element)
    comment_reference.append(comment_reference_run)

    elements[0].append(comment_reference)
要使用Python技术读取并解析.docx.doc文件,首先推荐阅读《Python实现docx/doc文件内容抓取与代码详解》这份资料。文档详细介绍了利用Python实现这一功能的方法和步骤,并提供了代码示例。 参考资源链接:[Python实现docx/doc文件内容抓取与代码详解](https://wenku.csdn.net/doc/6412b54dbe7fbd1778d42ab2) 在实现这一功能时,我们主要关注以下几个步骤: 1. 对于.docx文件,由于它是ZIP格式的包文件,我们可以使用Python的`zipfile`模块将文件解压成临时目录,然后利用`xml.dom.minidom`模块解析其中的`document.xml`文件。以下是一个简化的代码示例: ```python import zipfile from xml.dom import minidom def parse_docx(file_path): # 解压.docx文件到临时目录 temp_dir = 'temp/' zipf = zipfile.ZipFile(file_path, 'r') zipf.extractall(temp_dir) zipf.close() # 解析document.xml获取文本内容 doc_xml = minidom.parse(temp_dir + 'word/document.xml') content = doc_xml.getElementsByTagName('w:t') # 提取并拼接所有文本节点的内容 text_content = ''.join([node.getAttribute('space').strip() for node in content if node.getAttribute('space') != 'preserve']) # 清理临时文件 shutil.rmtree(temp_dir) return text_content # 使用函数解析.docx文件 docx_file = 'example.docx' docx_content = parse_docx(docx_file) print(docx_content) ``` 2. 对于.doc文件,可以先将其转换为.docx格式,或者使用其他库如`win32com`来直接处理,但这里我们只关注使用Python标准库的解决方案。 3.解析过程中,正则表达式可以用来清洗XML标签,以便提取出纯文本内容。 ```python import re # 示例:使用正则表达式清洗文本 cleaned_text = re.sub(r'<[^>]+>', '', docx_content) print(cleaned_text) ``` 通过以上步骤,你可以读取并解析.docx文件中的内容。对于.doc文件,虽然这里没有给出解决方案,但你可以根据类似思路,利用Python的文件操作库进行尝试。同时,如果你需要处理更复杂的文档结构或者对文档内容有更多定制化需求,可能需要考虑使用专门的库如python-docx,或者对上述代码进行进一步的扩展和优化。 《Python实现docx/doc文件内容抓取与代码详解》不仅为你提供了解析.docx文件的基本思路和方法,还鼓励你在遇到问题时分享解决方案,这对于学习和掌握Python处理文档内容非常有帮助。在深入学习之后,你还应当探索更多的库和工具,以便更高效地处理复杂文档。 参考资源链接:[Python实现docx/doc文件内容抓取与代码详解](https://wenku.csdn.net/doc/6412b54dbe7fbd1778d42ab2)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值