工作中正好接到个小项目需要将Excel数据导出组成word表格并合并所有表格成一个word文档,这个流程涉及到Excel的解析、Word文档的数据插入,多个Word文档的合并,对Word文档插入图片等,在尝试构写脚本时发现百度谷歌的资料多有缺失或者干脆就是过时的方法,于是写下这篇文章将自己在构写这个脚本时遇到的坑写一下。
首先是解析Excel数据,Python的xlrd框架可以快速地帮助我们完成这一部分操作:
import xlrd
//获取Excel文档
workbook = xlrd.open_workbook(u'2019名单.xls')
//我们可以通过两种不同的方式获取Excel文档中对应的表单
sheet = workbook.sheets()[0]
sheet = getSheetByName("表单名字",workbook)
//通过xlrd的接口获得表单的行数及列数
nrow = sheet.nrows
ncol = sheet.ncols
for i in range(1,nrow):
//cells代表的是Excel表单的每一行,即一个单元格组
cells = sheet.row(i)
//得到单元格组我们便可执行下一步的插入Word文档操作了
insertCellsToWord(cells)
需要明白的是,最好不要在Python脚本中对Word文档的效果进行再定义,原因之一是因为脚本并不全能,如果在脚本中对文档效果进行再定义,代码会变得臃肿且难以维护,例如先通过脚本生成表单效果再对单元格进行修改效果等等,这些操作最好先自行创建Word文档进行定义,即,通过Python脚本获取预先定义好的Word文档进行插入数据的操作。
譬如,我们有如下一张Word文档。
我们需要填充的数据都在这个表单里,通过预先确定的唯一标示符(即图中的英文单词)将Excel中的数据插入到对应的单元格中。
//python-docx是Python较为常用的操控Word文档的工具框架
from docx import Document
def insertCellsToWord(cells):
//获取预先设置的模板
test = Document("XXXX模板.docx")
for t in test.tables:
for row in t.rows:
//遍历到单元格进行数据插入
for cell in row.cells:
try:
set_cell(cell,cells)
except Exception as e:
print(cells)
print(e)
test_number = str(cells[0].value)
//生成新模板
test.save(output_path + "普通/" + test_number + ".docx")
def set_cell(cell,cells):
//找到对应的单元格进行数据插入
if cell.text == "school":
cell.text = cells[13].value
需要注意的是,当你如此完成了插入数据的操作并预览输出的Word文档时,你会发现,输出的文档并没有按照你预先调制的展示效果展示,例如单元格中的文字居中,加粗,字体修改等等,通过python-docx的操作会丢失这部分数据,cell.text = cells[13].value
的操作并不会继承效果,因此我们需要通过python-docx再次对单元格效果进行设置。
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Pt
if cell.text == "school":
cell.text = cells[13].value
p = cell.paragraphs[0]
p.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = p.runs[0]
r.bold = True
f = r.font
f.size = Pt(12)
通过单元格的paragraphs属性及runs属性进行样式设置,在此,我设置了单元格居中,文字加粗以及字体大小设置为12,你可以在python-docx的官方文档找到更多可参考的样式属性。
接下来开始进行Word文档合并。
在合并之前需要说明的是,我们常常会遇到表单中需要插入图片的需求,比如每张表格都有一个一寸照之类的,但在我构写这个脚本的踩坑中发现,图片并不能在合并文档之前插入(也可能是我没找到正确的方式),当先插入图片再进行合并时,导出的合并文件必然是损坏的,而导出没有图片的纯文本合并文件却不会损坏,因此,我通过在第一次对单元格的文本设置中对标注插入图片的单元格替换唯一标示符(例如上图中的Icon变为Icon+“对应的人员编号”),然后在合并文件之后再次遍历单元格插入图片,理所应当的,这样会导致脚本的效率大大降低,如果有更好的方式希望不吝赐教。
normal_path = output_path + "普通/"
//获取本地所有待合并文档
files = os.listdir(normal_path)
files.sort()
normal_merge_docx = ""
for index, filename in enumerate(files):
if index == 0:
normal_merge_docx = Document(normal_path + filename)
continue
sub_doc = Document(normal_path + filename)
for el in sub_doc.element.body:
normal_merge_docx.element.body.append(el)
normal_merge_docx.save("normal_merge_docx.docx")
可能会有人问,为什么要获取第一个待合并文件对接下来的文档进行合并而不是新建一个Word文档,对所有的待合并文件进行合并呢?
原因在于,新建的Word文档并不包含一些高于表格的样式数据,如果使用新建文档对所有待合并文档合并,则预览输出文件会发现合并Word文档的样式错误,所以必须取一个待合并文档作为基类来合并其他文档,这样才保证不会样式丢失。
接着进行图片插入:
from docx.shared import Inches
//因为我需要插入的图片位置是确定的,所以我直接设置入参去取对应的单元格插入图片,增加效率。
def insertImage(filename,row,col):
output = Document(filename)
for t in output.tables:
cell = t.cell(row,col)
if "-" in cell.text:
print(cell.text)
arr = cell.text.split("-")
cell.text = ""
p = cell.paragraphs[0]
r = p.runs[0]
pathstr = ""
//getImage通过具体的标识符去取本地图片,若本地图片有问题则通过dealImg对图片处理再行插入
try:
pathstr = getImg(arr[0], arr[1])
except Exception as e:
print(e)
pathstr = dealImg(getImg(arr[0], arr[1]), arr[1])
r.add_picture(pathstr, width=Inches(0.84), height=Inches(1.2))
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
需要注意的是,python-docx的文档告诉我们,插入图片的方法在单元格的paragraphs
数组属性的runs
数组属性中,只有找到具体的run属性才能add_picture
。
这里有一个问题,虽然在上述代码中我们将对应单元格的标记文本(Icon-XXXXXXX)置为空,但对应的样式属性依然存在,如果通过cell
的add_paragraph
增加paragraph
再通过add_run
增加run
的样式属性,则会发现输出文档的图像上多出一行,这是因为这个单元格的paragraphs数组变大了,即有两组样式属性对应不同文本,而在python-docx中又无法在不销毁单元格的情况下对单元格内的文本及样式属性进行快速清空,因此,就需要在原来的标记文本置为空的情况下获取对应的样式属性进行插入图片。
最后输出文件:
output.save(filename)
整个流程便结束了。