使用python的zipfile模块巧解word批量生成问题

  • 任务需求
  • 任务拆解
  • 任务方案
    • 方案一:使用python-docx.Document读取word文档
    • 方案二:zipfile巧解word文档
    • 方案三:MailMerge批量生成docx文档
  • 总结
  • 平台:windows10
  • 解释器:python3.7

任务需求

现有一包含目标数据的excel文档,需要将其中的每一行数据的对应内容替换到指定word中,并逐一保存。

任务拆解

首先查看word文档格式,可以看到文件后缀为.doc,需要替换的是正文部分红框中的英文部分。

目标数据excel文档,将excel中的对应列下的数据替换到word文档中。

excel数据比较规整无需二次处理,如果列名与word文档中不对应或者没有,则需要调整或新增。如此一来只要考虑如何读取doc文件并按一定逻辑进行替换即可。

任务方案

方案一:使用python-docx.Document读取word文档

在这次需求之前我并没有用过python操作word文档,感谢交流群内各位大佬相助,从Python+Excel+Word一秒制作百份合同(htps://mp.weixin.qq.com/s/B6s4RpfC3UvpJBitVVDOjg)文章中虽然跟着步伐编写代码,而当执行时报错了,目标字符串没有被替换上等等。

  • 问题一: 模块安装错误,文章中import docx,我误以为pip install docx就行了,而调用Document类时,发现模块下无该类,遂进行百度,应当时pip install python-docx,import docx。
  • 问题二: python-docx模块不能操作doc文档,上述已提到,本次处理的word文档为doc后缀,需要将其转换docx后缀方可正常操作,其实一个文档通过word软件进行另存为即可,但是在python编程中就显得不太优雅,主要是我太懒了,最多就将目标文件路径拷贝至代码中,所以使用win32com模块调用word程序转换doc文档为docx文档。
  • 问题三:Python+Excel+Word一秒制作百份合同中是定位到具体文本段在进行替换,首次尝试时,发现并不能替换成功,将代码逐步运行定位问题所在。可以想象下Document是将整个word文档分成多个paragraphs,一个paragraphs有很多行,每行有多个文本块,由于每行中的文本块的划分不太明白,中英文输入法不同方式输入的中/英文会导致本是一个单词被拆开,也有可能是该word文档中含有一定格式造成,如下划线,在无下划线的情况下,单词没有被分开,尝试用paragraphs.text进行内容的替换,文本可以替换成功,但下划线的格式被丢弃,所以只能采取文本块下的text方法进行替换,在原word文件中用同一种输入法输入英文(与excel的列名相对应,应保证该字符串不在word中其他地方出现,即中文也是可以的,推荐写法:#列名#)

将上述问题逐一解决后,输入目标文件路径及输入路径就大功告成了。
源码:

from copy import deepcopy
from pathlib import Path
from win32com import client as wc  # pip install pypiwin32
from docx import Document  # pip install python-docx
import pandas as pd# python-docx不能处理doc文档,使用win32com转存为docx文档
def doctransform2docx(doc_path):docx_path = doc_path + 'x'suffix = doc_path.split('.')[1]assert 'doc' in suffix, '传入的不是word文档,请重新输入!'if suffix == 'docx':return Document(doc_path)word = wc.Dispatch('Word.Application')doc = word.Documents.Open(doc_path)doc.SaveAs2(docx_path, 16)  # docx为16doc.Close()word.Quit()return Document(docx_path)# 替换docx中的特定字符,由于run方法在有格式的docx文件中展示效果很差,故将docx中的文本的需要填充出英文字符占位
def replace_docx(name, values, wordfile, path_name='Company'):wordfile_copy = deepcopy(wordfile)  # 防止原文件被篡改,deepcopy为副本for col_name, value in zip(name, values):if col_name == 'Company':  # 此处为设置文件名,按业务需求可进行修改path_name = str(value)for paragraphs in wordfile_copy.paragraphs:for run in paragraphs.runs:run.text = run.text.replace(col_name, str(value))# docx文档替换完毕,另存为,一定要用绝对路径wordfile_copy.save(f'{save_folder}/{path_name}.docx')if __name__ == '__main__':# 定义需处理的文件路径doc_path = r"D:\solve_path\单位.doc"excel_path = r"D:\solve_path\信息.xls"save_folder = Path('D:/docx_save')save_folder.mkdir(parents=True, exist_ok=True)  # 文件夹没有时自动创建# 获取excel数据data = pd.read_excel(excel_path)wordfile = doctransform2docx(doc_path)data_save = data.apply(lambda x: replace_docx(x.index, x.values, wordfile), axis=1)

在我以为大功告成之际,问题来了,原文档中的方框没了(漏!!!)
效果图:

解决了格式却解决不了特殊字符问题,秃了啊……,我想python-docx中一定有相应的解决方案,但是我初次尝试,对其中源码部分犹如天书般的存在,在多次调用方法下发现其中的一个参数输出,wordfile.part.blob:

输出内容让我想起了之前解密excel时看到的文件开头,xml文件,然后首先尝试替换其中文本,原以为会像run.text = run.text.replace(col_name, str(value))一样即可,然而报错了,禁止修改。

方案二:zipfile巧解word文档

正当我认为别无他法时,就此作罢时,百度百科帮助了我:

docx文档本质上就是xml文件,emmmm,很妙,之前为了提取xlsx中的图片解压缩过xlsx文件然后提取,果然可行,替换的主体文件就是word文件夹下的document.xml文件

当然在代码编写前首先尝试能不能手动复原为docx,用7z默认参数还原失败,经过多番寻找,用zip类型压缩即可,软件不限,手动解压及替换字符压缩均成功,开始敲代码。
除习惯性用pandas读取excel文件外,也不用安装其他包,在现用python3.7中均为内置包。
使用zipfile对压缩类文件进行解压,文章学习来源:python中如何压缩和解压缩文件,文章中写得很详细,我仅把os.path改写成pathlib。
但在对目录下文件进行压缩还原至docx文档时出现了问题:

  • 问题一: python中如何压缩和解压缩文件中压缩文件类型为
    zipfile.ZIP_DEFLATED,对遍历后的所有文件进行压缩至一个目录下,这就出现了还原后的docx内的文件层次不对应,docx读取失败。改用zipfile.zlib.DEFLATED方可成功按层次压缩。
  • 问题二:zipfile压缩文件保存时,应当有文件名及其别名,且别名不能为绝对路径,为了能正常还原也应使用原有名称,在代码中为f.write(文件路径, 文件路径别名)

源码:

from shutil import rmtree
import zipfile
from copy import deepcopy
from pathlib import Path
from win32com import client as wc  # pip install pypiwin32
import pandas as pd# doc文档不包含所需xml文件,使用win32com转存为docx文档,返回值与方案一有所不同
def doctransform2docx(doc_path):docx_path = doc_path + 'x'suffix = doc_path.split('.')[1]assert 'doc' in suffix, '传入的不是word文档,请重新输入!'if suffix == 'docx':return Path(doc_path)word = wc.Dispatch('Word.Application')doc = word.Documents.Open(doc_path)doc.SaveAs2(docx_path, 16)  # docx为16doc.Close()word.Quit()return Path(docx_path)# docx文档解压
def docx_unzip(docx_path):unzip_path = docx_path.with_name(docx_path.stem)with zipfile.ZipFile(docx_path, 'r') as f:for file in f.namelist():f.extract(file, path=unzip_path)xml_path = unzip_path.joinpath('word/document.xml')with xml_path.open(encoding='utf-8') as f:xml_file = f.read()return unzip_path, xml_path, xml_file# 讲文件夹中的所有文件压缩成docx文档
def docx_zipped(docx_path, zipped_path):with zipfile.ZipFile(zipped_path, 'w', zipfile.zlib.DEFLATED) as f:for file in docx_path.glob('**/*.*'):f.write(file, file.as_posix().replace(docx_path.as_posix() + '/', ''))# 删除生成的解压文件夹
def remove_folder(path):if path.exists():rmtree(path)# 替换docx中的特定字符,重新保存document.xml至需要压缩的目录下
def replace_docx(name, values, xml_file, xml_path, unzip_path, path_name='Company'):xml_file_copy = deepcopy(xml_file)  # 深复制xml内容for col_name, value in zip(name, values):if col_name == 'Company':  # 此处为设置文件名,按业务需求可进行修改path_name = str(value)xml_file_copy = xml_file_copy.replace(col_name, str(value))with xml_path.open(mode='w', encoding='utf-8') as f:f.write(xml_file_copy)# xml文档替换完毕,通过zipfile重新压缩另存为docx文档docx_zipped(unzip_path, f'{save_folder}/{path_name}.docx')if __name__ == '__main__':# 定义需处理的文件路径doc_path = r"D:\solve_path\单位.doc"excel_path = r"D:\solve_path\信息.xls"save_folder = Path('D:/docx_save')save_folder.mkdir(parents=True, exist_ok=True)  # 文件夹没有时自动创建# 获取excel数据data = pd.read_excel(excel_path)docx_path = doctransform2docx(doc_path)unzip_path, xml_path, xml_file = docx_unzip(docx_path)data_save = data.apply(lambda x: replace_docx(x.index, x.values, xml_file, xml_path, unzip_path), axis=1)remove_folder(unzip_path)

打开生成的文件,方框没有消失,下划线也在。

更新

方案三:MailMerge批量生成docx文档

  • word版本:word 2016

需要将修改部分设置成域格式:
①:在“插入”菜单项中选择文档部件中的域

②:域格式选择,按图从左至右依次点击,域名按业务自定义命名,最后点击确定即可

③:域格式设置成功,可以看到之前的英文被类似于书名号的引用符包裹起来了

④:依次将剩下的字符修改完成
⑤:同样为依赖docx,如处理文档为doc格式需要转换为docx文档,excel文件内容设置已在上述提过,此处不在赘述,编写代码,批量生成文件
源码:

import docx
from mailmerge import MailMerge  # pip install docx-mailmerge
from win32com import client as wc
from pathlib import Path
import pandas as pd# mailmerge不能处理doc文档,使用win32com转存为docx文档
def doctransform2docx(doc_path):docx_path = doc_path + 'x'suffix = doc_path.split('.')[1]assert 'doc' in suffix, '传入的不是word文档,请重新输入!'if suffix == 'docx':return Path(doc_path)word = wc.Dispatch('Word.Application')doc = word.Documents.Open(doc_path)doc.SaveAs2(docx_path, 16)  # docx为16doc.Close()word.Quit()return Path(docx_path)# 替换docx中的特定字符,重新保存document.xml至需要压缩的目录下
def replace_docx(data_dict, docx_path, path_name='Company'):with MailMerge(docx_path) as docx_file:path_name = data_dict.get('Company')  # 此处为设置文件名,按业务需求可进行修改# 字段替换主体代码,业务需求变化必须更改以下代码docx_file.merge(Place=data_dict.get('Place'),Company=data_dict.get('Company'),Credit_Code=data_dict.get('Credit_Code'),Representative=data_dict.get('Representative'))docx_file.write(f'{save_folder}/{path_name}.docx')if __name__ == '__main__':# 定义需处理的文件路径doc_path = r"D:\solve_path\单位.doc"excel_path = r"D:\solve_path\信息.xls"save_folder = Path('D:/docx_save')save_folder.mkdir(parents=True, exist_ok=True)  # 文件夹没有时自动创建# 获取excel数据data = pd.read_excel(excel_path)data = data.astype(str)  # 将数据类型强制转换为str类型,避免word替换出错docx_path = doctransform2docx(doc_path)data_save = data.apply(lambda x: replace_docx(x.to_dict(), docx_path), axis=1)

替换后效果图,可以看到word中格式没有被打乱。

这种方法在整个流程操作下非常顺滑,代码量也相对较少,但是复用性较差,目前我暂未发现能将该代码打包成exe,让其他人能够自定义需要修改的字段名,只能针对于一种需求,每次更改需求就要更改主体代码.merge方法中的参数,对于面向未来编程不太优雅。

适用性排序:方案二 > 方案一 > 方案三
简易性排序:方案三 > 方案二 > 方案一

PS: 如果电脑安装的是WPS,可以尝试手动转换doc格式为docx格式,再进行批量操作。

总结

经过几番尝试后,也是我的学识不深,在跌跌撞撞中找到一种既能替换docx中的字符串也不会改变原有格式的方案,相信一定会有更好的方案,只是此时我没有找到,时间是不停地向前的,我也不应落下,以求共同富贵。如对此有疑问或者见解欢迎评论区留言。


于二零二一年十一月二十八日作

使用python的zipfile模块巧解word批量生成问题相关推荐

  1. python之sys模块详解_(转)python之os,sys模块详解

    python之sys模块详解 原文:http://www.cnblogs.com/cherishry/p/5725184.html sys模块功能多,我们这里介绍一些比较实用的功能,相信你会喜欢的,和 ...

  2. 【Python】python之subprocess模块详解

    [Python]python之subprocess模块详解 subprocess模块 subprocess是Python 2.4中新增的一个模块,它允许你生成新的进程,连接到它们的 input/out ...

  3. Python的pickle模块详解(包括优缺点及和JSON的区别)

    文章目录 一.pickle是什么? 1.pickle的优缺点 2.pickle和JSON的区别 3.pickle的应用总结 二.pickle的用法 1. pickle接口 2. pickle实例 结语 ...

  4. python批量生成word报告_Python操作Word批量生成合同的实现示例

    背景:大约有3K家商家需要重新确认信息并签订合同.合同是统一的Word版本.每个供应商需要修改合同内的金额部分.人工处理方式需要每个复制粘贴且金额要生成大写金额.基于重复工作可偷懒.用Python解救 ...

  5. python制作合同模板带图片_办公自动化7_用Python操作Word批量生成合同

    ########## 实例7:用Python操作Word批量生成合同 ################ ''' 来源网上整理 ,供学习使用. 我们建立的模板和合同信息如下图所示:这里有几个注意事项: ...

  6. python制作合同模板带图片_Python操作Word批量生成合同的实现示例

    背景:大约有3K家商家需要重新确认信息并签订合同.合同是统一的Word版本.每个供应商需要修改合同内的金额部分.人工处理方式需要每个复制粘贴且金额要生成大写金额.基于重复工作可偷懒.用Python解救 ...

  7. python zipfile_python zipfile模块学习笔记(一)

    ZIP文件格式是一种常见的存档和压缩标准,这个zipfile模块提供了工具来创建.读取.写入.附加和列出一个ZIP文件.使用ZIP64扩展(即压缩文件大小超过4G),它能解压加密的ZIP文件,解密过程 ...

  8. python中logging模块详解_python logging日志模块详解

    logging 日志模块详解 用Python写代码的时候,在想看的地方写个print xx 就能在控制台上显示打印信息,这样子就能知道它是什么了,但是当我需要看大量的地方或者在一个文件中查看的时候,这 ...

  9. Python的PyYAML模块详解

    文章目录 注:原文链接 简介 安装 简单安装 从源码安装 最常被问到的问题 使用详解 加载YAML 转储YAML 构造.表示和解析 YAML语法 文档 块序列 块映射 流集合 标量 别名 标签 YAM ...

最新文章

  1. mxnet 查看中间层结果
  2. python降序排列说true不存在_Python数据类型串讲(中)
  3. MFC-Tab Control控件使用
  4. 嵌入式RT3070 AP模式移植
  5. struts2 mysql 乱码_struts2项目插入中文到mysql数据库乱码的解决方法
  6. qmake生成VS的vcproj/sln工程文件
  7. rust 手动关闭子线程_Rust入坑指南:齐头并进(上)
  8. 大学c语言程序设计上机题库,c语言上机题库(c语言程序设计基础题库)
  9. Chrome如何下载网页视频
  10. Mathtype在word中编辑公式时变成英文
  11. 20155314 2016-2017-2 《Java程序设计》第2周学习总结
  12. 无法使用XXXX附加到应用程序解决办法
  13. Android项目:基于安卓Android平台手机商城系统app(计算机毕业设计)
  14. 怎么用真机测试android,andriod studio如何使用真机测试 andriod studio真机测试教程
  15. react 学习之Protal
  16. 关于 OneNote 无法打开笔记本 无法打开该位置 没有该位置权限:
  17. 【答学员问】面试问题-毕业时候为什么没有选择开发
  18. linux查看映射文件关系,Linux磁盘映射DM
  19. windows11/windows10设置移动热点自启(图文步骤教程)
  20. 访问学者申请德国签证如果丢失了怎么办?

热门文章

  1. 创建Springboot项目、java项目遇到的问题
  2. java finalize方法总结、GC执行finalize的过程
  3. redis rua解决库存问题_Redis事务
  4. 2022/1/23(每周总结)
  5. 直流马达驱动_PWM加减速(STM32F4 CubeMX)
  6. java 中 IO 的流的种类及BIO、NIO、AIO 有什么区别?
  7. Qt-qrc资源文件-rcc打包-程序调用-ZIP压缩和解压-安装程序制作参考
  8. eclipse打不开的问题
  9. AltiumDesigner下载安装
  10. 输入空间、输出空间、特征空间与假设空间的区分