如何使用python-docx替换Word文档中的文本并保存

62

在同一页提到的 oodocx 模块指向一个似乎不存在的 /examples 文件夹。
我已经阅读了 python-docx 0.7.2 的文档,以及在 Stackoverflow 上搜索到的所有相关内容,请相信我已经完成了我的“功课”。

Python 是我唯一会的语言(初学者+,也许是中级水平),所以请不要假设我懂 C、Unix、xml 等任何知识。

任务:用单行文本打开一个仅包含一行文本的 ms-word 2007+ 文档(为了保持简单)并将字典中出现在该文本行中的任何“关键”词替换为其字典值。然后关闭文档,并保持其他内容不变。

文本行(例如):“我们将逗留在海洋的房间里。”

from docx import Document

document = Document('/Users/umityalcin/Desktop/Test.docx')

Dictionary = {‘sea’: “ocean”}

sections = document.sections
for section in sections:
    print(section.start_type)

#Now, I would like to navigate, focus on, get to, whatever to the section that has my
#single line of text and execute a find/replace using the dictionary above.
#then save the document in the usual way.

document.save('/Users/umityalcin/Desktop/Test.docx')

我在文档中没有看到任何允许我这样做的内容——也许是有的,但由于所有东西都没有按照我的水平详细说明,所以我没有理解。

我已经尝试了本站上的其他建议,并尝试使用模块的早期版本(https://github.com/mikemaccana/python-docx),该版本应该具有“replace、advReplace”等方法,操作如下:我在python解释器中打开源代码,并在末尾添加以下内容(这是为了避免与已安装的版本0.7.2发生冲突):

document = opendocx('/Users/umityalcin/Desktop/Test.docx')
words = document.xpath('//w:r', namespaces=document.nsmap)
for word in words:
    if word in Dictionary.keys():
        print "found it", Dictionary[word]
        document = replace(document, word, Dictionary[word])
savedocx(document, coreprops, appprops, contenttypes, websettings,
    wordrelationships, output, imagefiledict=None) 

运行此代码会出现以下错误信息:

NameError:未定义名称'coreprops'

也许我正在尝试做一些不可能的事情,但如果我错过了什么简单的东西,我会感激您的帮助。

如果这很重要,我正在使用Enthought的Canopy 64位版本,在OSX 10.9.3上运行。

11个回答

78

更新:有几个段落级函数可以很好地完成这个任务,并且可以在python-docx的GitHub网站上找到。

  1. 这个函数将会用替换字符串替换正则表达式匹配。替换字符串的格式与匹配字符串的第一个字符相同。
  2. 这个函数将会隔离一组字符,以便对该单词或短语应用某些格式,例如在文本中突出显示每个"foobar"的出现次数,或者使其变为粗体或更大的字体。

当前版本的python-docx没有search()函数或replace()函数。虽然这两个功能经常被请求,但是实现通用情况下的实现非常棘手,并且还没有成为待办事项中最重要的任务。
一些人已经成功地利用已有的工具完成了他们需要的工作。以下是一个示例。顺便说一下,它与章节无关 :)
for paragraph in document.paragraphs:
    if 'sea' in paragraph.text:
        print paragraph.text
        paragraph.text = 'new text containing ocean'

要在表格中进行搜索,您需要使用类似以下方式的内容:
for table in document.tables:
    for row in table.rows:
        for cell in row.cells:
            for paragraph in cell.paragraphs:
                if 'sea' in paragraph.text:
                    paragraph.text = paragraph.text.replace("sea", "ocean")

如果你选择这条路,很快就会发现其中的复杂性。如果你替换段落的整个文本,那么会删除任何字符级别的格式,比如加粗或斜体的单词或短语。
顺便说一下,@wnnmaw回答中的代码适用于旧版本的python-docx,在0.3.0之后的版本中根本无法使用。

感谢澄清,这节省了很多时间。我将会是等待那些功能升级到列表顶部的众多人之一,同时尝试使用“遗留”版本来完成我需要做的事情。顺便问一下,当前版本中是否有任何东西可以让我删除段落中的单词“sea”,并在其位置插入另一个单词?可能没有,因为如果有的话,即使是我也能编写一个“替换”函数...问候 - user2738815
1
完全正确。如果那是这样的话,那就很容易了。问题在于,“sea”可能单独出现在一个<w:t>元素中,也可能分成两个甚至三个部分,并且甚至可能出现在不同的运行(<w:r>元素,父级为t元素)中。替换一个单词需要重新组合包含它的元素。有很多可能的情况和规则来控制如何将其重新组合而不会搞砸它。如果情况简单,你可以通过简单地重写文本来完成,但否则这是一项相当大的工作。如果您满意,请不要忘记投票并接受答案 :) - scanny
显然,我不能投票,因为我缺少"声望",但是我很感谢你的帮助,并且我已经检查接受了答案。敬礼。 - user2738815
2
参考文献 - 这是关于该问题的 Github 实际讨论:https://github.com/python-openxml/python-docx/issues/30 - Grzegorz Oledzki
有没有嵌套表的解决方案? - user3260061

35

我需要一些东西来替代docx中的正则表达式。 我采用了scannys的答案。 为了处理样式,我使用了以下答案: Python docx Replace string in paragraph while keeping style 添加了递归调用以处理嵌套表格。 然后得到了这样的结果:

import re
from docx import Document

def docx_replace_regex(doc_obj, regex , replace):

    for p in doc_obj.paragraphs:
        if regex.search(p.text):
            inline = p.runs
            # Loop added to work with runs (strings with same style)
            for i in range(len(inline)):
                if regex.search(inline[i].text):
                    text = regex.sub(replace, inline[i].text)
                    inline[i].text = text

    for table in doc_obj.tables:
        for row in table.rows:
            for cell in row.cells:
                docx_replace_regex(cell, regex , replace)



regex1 = re.compile(r"your regex")
replace1 = r"your replace string"
filename = "test.docx"
doc = Document(filename)
docx_replace_regex(doc, regex1 , replace1)
doc.save('result1.docx')

遍历字典的方法:

for word, replacement in dictionary.items():
    word_re=re.compile(word)
    docx_replace_regex(doc, word_re , replacement)

请注意,此解决方案仅在文档中整个正则表达式具有相同样式时才替换正则表达式。

此外,如果在保存后编辑文本,则相同样式的文本可能位于不同的运行中。例如,如果您打开包含“testabcd”字符串的文档并将其更改为“test1abcd”,然后保存,即使它们是相同的样式,也会有3个单独的运行“test”、“1”和“abcd”。在这种情况下,test1的替换将无法工作。

这是为了跟踪文档中的更改。要将其合并为一个运行,请在Word中转到“选项”,“信任中心”,在“隐私选项”中取消选中“存储随机数字以提高组合精度”,然后保存文档。


这个方案在你提到的限制范围内是可行的,我已经点赞了。然而,编辑一下你的代码以展示如何传递一个字典会更有用。我检查过了,可以实现,但需要对正则表达式进行微调。我不想发一个单独的答案。谢谢。 - user2738815
2
更新了字典示例,并添加了如何将编辑合并为一个运行的说明。干杯。 - szum
谢谢。我正在使用2.7版本,word_re = re.compile(word)会引发错误。相反,word_re = re.compile(str(word))可以正常工作。我不知道这是否是版本相关的差异,因为我不了解Python 3的工作方式。 - user2738815
感谢@szum提供的解决方案,它完美地运行了,但我注意到它忽略了WordArt文本或文本框内的文本,您能否在此基础上添加支持呢? - Johnn Kaita
我遇到了以下错误:Traceback (most recent call last): gen_docx(input, dictionary, output) docx_replace_regex(document, word_re, replacement) text = regex.sub(replace, inline[i].text) template = _compile_repl(template, pattern) return sre_parse.parse_template(repl, pattern) s = Tokenizer(source) string = str(string, 'latin1') TypeError: decoding to str: need a bytes-like object, int found你知道如何解决吗?谢谢。 - Steven Lee

34

我分享了一个小脚本 - 它可以帮助我生成带有变量的法律.docx合同,同时保留原始样式。

pip install python-docx

例子:

from docx import Document
import os


def main():
    template_file_path = 'employment_agreement_template.docx'
    output_file_path = 'result.docx'

    variables = {
        "${EMPLOEE_NAME}": "Example Name",
        "${EMPLOEE_TITLE}": "Software Engineer",
        "${EMPLOEE_ID}": "302929393",
        "${EMPLOEE_ADDRESS}": "דרך השלום מנחם בגין דוגמא",
        "${EMPLOEE_PHONE}": "+972-5056000000",
        "${EMPLOEE_EMAIL}": "example@example.com",
        "${START_DATE}": "03 Jan, 2021",
        "${SALARY}": "10,000",
        "${SALARY_30}": "3,000",
        "${SALARY_70}": "7,000",
    }

    template_document = Document(template_file_path)

    for variable_key, variable_value in variables.items():
        for paragraph in template_document.paragraphs:
            replace_text_in_paragraph(paragraph, variable_key, variable_value)

        for table in template_document.tables:
            for col in table.columns:
                for cell in col.cells:
                    for paragraph in cell.paragraphs:
                        replace_text_in_paragraph(paragraph, variable_key, variable_value)

    template_document.save(output_file_path)


def replace_text_in_paragraph(paragraph, key, value):
    if key in paragraph.text:
        inline = paragraph.runs
        for item in inline:
            if key in item.text:
                item.text = item.text.replace(key, value)


if __name__ == '__main__':
    main()

在此输入图片描述


是否有一种方法可以同时插入/替换图像? - Rami
@Rami 我认为是的。请针对这个用例提出一个专门的问题(随时私信我,我会尽力回答)。 - Jossef Harush Kadouri

20

我从先前的答案中得到了很多帮助,但对于我来说,下面的代码的功能就像Word中简单的查找和替换功能一样。希望这能有所帮助。

#!pip install python-docx
#start from here if python-docx is installed
from docx import Document
#open the document
doc=Document('./test.docx')
Dictionary = {"sea": "ocean", "find_this_text":"new_text"}
for i in Dictionary:
    for p in doc.paragraphs:
        if p.text.find(i)>=0:
            p.text=p.text.replace(i,Dictionary[i])
#save changed document
doc.save('./test.docx')

上述解决方案有局限性。1)包含“find_this_text”的段落将成为没有任何格式的纯文本,2)与“find_this_text”在同一段落中的上下文控件将被删除,3)位于上下文控件或表格中的“find_this_text”不会更改。


这太棒了! - Jem
这很完美 :)非常感谢。 - Manthan_Admane
哦,天啊...这正是我所需要的...非常感谢你... - Pragyan Choudhury
这很棒。谢谢,伙计。但是这在表格上不起作用。 - mending3
正是我所需要的。谢谢! - Danny Blaker

2

对于表格情况,我需要修改@scanny的答案:

for table in doc.tables:
    for col in table.columns:
        for cell in col.cells:
            for p in cell.paragraphs:

让它起作用。实际上,这似乎在API的当前状态下无法正常工作:

for table in document.tables:
    for cell in table.cells:

这段代码与此处的代码存在相同问题:https://github.com/python-openxml/python-docx/issues/30#issuecomment-38658149


1

这个库 python-docx-template 对于这个任务非常有用。它非常适合编辑 Word 文档并将其保存为 .docx 格式。


1

Office Dev Centre有一篇文章,其中一位开发者发布了一些算法的描述,这些算法似乎提出了一个解决方案(虽然是用C#编写的,并需要移植):"MS Dev Centre posting"(目前采用MIT许可证)。


非常有趣的Soferio!非常感谢您提到这一点;我会仔细研究它,看是否可以包含在库中 :) - scanny

0
import docx2txt as d2t
from docx import Document
from docx.text.paragraph import Paragraph
document = Document()
all_text = d2t.process("mydata.docx")
# print(all_text)
words=["hey","wow"]
for i in range words:
        all_text=all_text.replace(i,"your word variable")
        document.add_paragraph(updated + "\n")
        print(all_text)
document.save('data.docx')

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

0
你第二次尝试的问题在于你没有定义savedocx需要的参数。在保存之前,你需要做类似这样的事情:
relationships = docx.relationshiplist()
title = "Document Title"
subject = "Document Subject"
creator = "Document Creator"
keywords = []

coreprops = docx.coreproperties(title=title, subject=subject, creator=creator,
                       keywords=keywords)
app = docx.appproperties()
content = docx.contenttypes()
web = docx.websettings()
word = docx.wordrelationships(relationships)
output = r"path\to\where\you\want\to\save"

非常感谢您的回复。我立即在“保存”之前添加了您的代码,只将输出路径更改为“/Users/umityalcin/Desktop/”(我假设保留标题等不重要)。然而,我遇到了其他问题。首先,由于我没有导入当前的docx模块(0.7.2)以避免错误,解释器无法识别“docx.”前缀。所以我导入了该模块 - 现在我得到了这个错误:AttributeError:'module'对象没有属性'relationshiplist'。感谢您的时间和帮助。 - user2738815
啊,好的,显然阅读不是我的强项 :P 如果你的程序作用域中有docx的所有函数,你就不需要使用docx.前缀,所以尝试将其删除。 - wnnmaw
好吧,至少编码不是你的弱项;似乎是我的 :) 在遵循你的建议后,我仍然遇到了这个错误:savedocx(document,coreprops,appprops,contenttypes,websettings,wordrelationships,output,imagefiledict)1061) )1062-> 1063请确保template_dir是目录 1064 docxfile = zipfile.ZipFile( 1065 output,mode ='w',compression = zipfile.ZIP_DEFLATED)断言错误: - user2738815

0

正如一些用户所分享的,Word文档中查找和替换文本时所面临的挑战之一是保留样式,如果一个单词跨越多个文本段落,则会出现这种情况,这可能是因为Word具有许多样式或者在创建文档时该单词被多次编辑。因此,一个简单的代码假设一个单词完全位于一个文本段落中是通常不成立的,因此基于python-docx 的代码可能无法适用于很多场景。

你可以尝试以下API

https://rapidapi.com/more.sense.tech@gmail.com/api/document-filter1

这里有适用于各种情况的通用代码。目前API仅支持段落文本,表格文本目前不支持,我会尽快尝试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接