手把手教你使用 Python 的 zipfile 模块巧解 Word 批量生成问题

今天给大家分享使用python的zipfile模块巧解word批量生成问题,这里提供两种方案给大家参考。

  • 方案一:使用python-docx.Document读取word文档
  • 方案二:zipfile巧解word文档
  • 平台:windows10
  • 解释器:python3.7

任务需求

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

任务拆解

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

图片

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

图片

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

任务方案

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

在这次需求之前我并没有用过python操作word文档,感谢交流群内各位大佬相助,虽然跟着步伐编写代码,而当执行时报错了,目标字符串没有被替换上等等。

  • 问题一:模块安装错误,文章中import docx,我误以为pip install docx就行了,而调用Document类时,发现模块下无该类,遂进行百度,应当时pip install python-docx,import docx。

  • 问题二:python-docx模块不能操作doc文档,上述已提到,本次处理的word文档为doc后缀,需要将其转换docx后缀方可正常操作,其实一个文档通过word软件进行另存为即可,但是在python编程中就显得不太优雅,主要是我太懒了,最多就将目标文件路径拷贝至代码中,所以使用win32com模块调用word程序转换doc文档为docx文档。

  • 问题三:文章中是定位到具体文本段在进行替换,首次尝试时,发现并不能替换成功,将代码逐步运行定位问题所在。可以想象下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为16
    doc.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对压缩类文件进行解压

但在对目录下文件进行压缩还原至docx文档时出现了问题:

  • 问题一:文章中的压缩文件为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为16
    doc.Close()
    word.Quit()
    return Path(docx_path)

# docx文档解压
def docx_unzip(docx_path):
    docx_path = Path(docx_path) if isinstance(docx_path, str) else docx_path
    upzip_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=upzip_path)
    xml_path = upzip_path.joinpath('word/document.xml')
    with xml_path.open(encoding='utf-8') as f:
        xml_file = f.read()
    return upzip_path, xml_path, xml_file

# 讲文件夹中的所有文件压缩成docx文档
def docx_zipped(docx_path, zipped_path):
    docx_path = Path(docx_path) if isinstance(docx_path, str) else docx_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):
    path = Path(path) if isinstance(path, str) else path
    if path.exists():
        rmtree(path)
    else:
        raise "系统找不到指定的文件"
            
# 替换docx中的特定字符,重新保存document.xml至需要压缩的目录下
def replace_docx(name, values, xml_file, xml_path, unzip_path, path_name='Company'):
    xml_path = Path(xml_path) if isinstance(xml_path, str) else xml_path
    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)

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

在这里插入图片描述

总结

经过几番尝试后,也是我的学识不深,在跌跌撞撞中找到一种既能替换docx中的字符串也不会改变原有格式的方案,相信一定会有更好的方案,只是此时我没有找到,时间是不停地向前的,我也不应落下,以求共同富贵。
PS: 如果电脑安装的是WPS,可以尝试手动转换doc格式为docx格式,再进行批量操作。

关于Python学习指南

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

包括:Python激活码+安装包、Python web开发,Python爬虫,Python数据分析,人工智能、自动化办公等学习教程。带你从零基础系统性的学好Python!

👉Python所有方向的学习路线👈

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。(全套教程文末领取)

在这里插入图片描述

👉Python学习视频600合集👈

观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

在这里插入图片描述

温馨提示:篇幅有限,已打包文件夹,获取方式在:文末

👉Python70个实战练手案例&源码👈

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

在这里插入图片描述

👉Python大厂面试资料👈

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

在这里插入图片描述

在这里插入图片描述

👉Python副业兼职路线&方法👈

学好 Python 不论是就业还是做副业赚钱都不错,但要学会兼职接单还是要有一个学习规划。

在这里插入图片描述

👉 这份完整版的Python全套学习资料已经上传,朋友们如果需要可以扫描下方CSDN官方认证二维码或者点击链接免费领取保证100%免费

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值