作者:Ryoko
来源:凹凸数据
不久前,一个同事有个项目要向领导交差,其中一部分工作是根据 excel 表中的每日数据,按格式整理成日报写入 word。
好家伙!足足 178 天的量要补,如果要靠复制粘贴,岂不是肝到吐血,(你给我自己解决啊!)
好吧ojbk,是时候祭出 Python 办公自动化了。

一、基础数据整理
首先让我们来看看数据样本和输出文档的需求(敏感数据已做和谐处理):原始 excel 文件中有 n 个子表,每个子表为一天的数据,存在无记录和有记录(部门数 ≥ 1,每个部门记录数 ≥ 1)两种情况,需分别整理成两种日报,一为纯文本描述,二为附带表格的文档。



撸起袖子,开骂!
哦不,开始写代码!
先将子表合成一个,便于统一观察每日数据记录的规律,也方便后期处理。使用 xlrd 库读表,获取工作簿中的活动表名,再使用 pandas 库遍历子表以合并,dataframe 格式的数据对 excel 表的相性绝佳。
def merge_sheet(filepath): # 合并多个同表头的子表 wb = xlrd.open_workbook(filepath) sheets = wb.sheet_names() df_total = pd.DataFrame() for name in sheets: df = pd.read_excel(filepath, sheet_name=name) df_total = df_total.append(df) df_total.to_excel("merge.xlsx", index=False)
二、输出两种日报
(一)纯文本文档
根据需要输出的日报样式,输出无记录的日报只需读取【日期】列和【填报部门】列,将【填报部门】列为无的日期段按每日输出即可。观察原表数据,直接筛选无填报记录的数据丢到命名为“无”的子表里。

这里也可以利用 .groupby()
对【填报部门】列分组,取“无”的那一组,可是要注意一点:虽然 Python 很强大,但不需要将所有事情都交给 Python 做。
导入库和模块如下:
import pandas as pdimport xlrdfrom docx import Documentfrom docx.shared import Ptfrom docx.shared import Inchesfrom docx.oxml.ns import qnfrom docx.enum.text import WD_PARAGRAPH_ALIGNMENTfrom docx.enum.section import WD_ORIENTATION
基本流程很简单,读入无填报记录的数据,按日期输出 word 文档。
def wu_to_word(filepath): df = pd.read_excel(filepath, sheet_name="无") date_list = list(df['日期']) for d in date_list: filename = wordname+str(d)+").docx" # 输出的word文件名 title = "("+str(d)[:4]+"."+str(d)[4:6]+"."+str(d)[6:8]+")" # 副标题日期XXXX.XX.XX word = str(d)[:4]+"年"+str(d)[4:6]+"月"+str(d)[6:8]+"日" # 开头、落款日期XXXX年XX月XX日 wu_doc(title, word, filename) print(f"文件:{filename},{title},{word} 已保存")
每份文档都会用到的同样的内容也可以先设定好。
wordname = “XX公司业务数据表(日报”
all_title = ” XX公司业务报告”
生成 word 内容,不加表格的情况下还是比较容易实现的,注意调整好格式。
def wu_doc(title,word,filename): # 传入副标题日期,文段开头及落款的日期,文件名 doc = Document() # 创建文档对象 section = doc.sections[0] # 获取页面节点section.orientation = WD_ORIENTATION.LANDSCAPE # 页面方向设为横版new_width, new_height = section.page_height, section.page_width # 将原始长宽互换,实现将竖版页面变为横版 section.page_width = new_width section.page_height = new_height # 段落的全局设置 doc.styles['Normal'].font.name = u'宋体' # 字体 doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体') # 中文字体需再添加这个设置 doc.styles['Normal'].font.size = Pt(14) # 字号 四号对应14 t1 = doc.add_paragraph() # 添加一个段落 t1.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 居中 _t1 = t1.add_run(all_title) # 添加段落内容(大标题) _t1.bold = True # 加粗 _t1.font.size = Pt(22) t2 = doc.add_paragraph() # 再添加一个段落 t2.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 居中 _t2 = t2.add_run(title + "\n") # 添加段落内容(副标题) _t2.bold = True doc.add_paragraph(word + "无记录。\n\n").paragraph_format.first_line_indent = Inches(0.35) # 添加段落同时添加内容,并设置首行缩进 doc.add_paragraph(word).paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT # 落款日期右对齐 doc.save(dir+filename) # 按路径+文件名保存
执行!104 份无填报记录的日报就写好啦,干脆就这样交差吧,剩下的不想研究了哈哈哈。

(二)附表格文档
有报送记录的数据处理起来相对复杂一点,先看一下原始数据。

比如 X 年 X 月 X 日,有 N 个部门填报了数据,根据文档样例,文段描述部分需要整理成如下格式:
部门 A:“报送内容1” X条记录;“报送内容2” Y条记录;部门 B:……;部门 C:……;
而附件表格部分需整理成如下格式,可以预想把每一行需要的数据整理一个 list,按行写入表格:
一级指标 | 二级指标 | 三级指标 | 四级指标 | 各部门报送情况 | 备注 |
---|---|---|---|---|---|
lalala | hahaha | balabala | 若为空则沿用上级 | 部门A:报送内容1 | 有记录未上传,没报,系统崩了 |
aaa | bbb | ccc | ddd | 部门A:报送内容2 | 已上传,报的好 |
… | … | … | … | 部门B:报送内容1 | … |
基本流程类似,读表后先按日期分组,每一组含一天中的一个或多个部门数据,再生成某一天的附件需要的表格,接着整理文段描述,最后按日期输出每一天的 word 文档。
def what_to_word(filepath): df = pd.read_excel(filepath, sheet_name="有") df.fillna('', inplace=True) # 替换nan值为空字符 dates = [] # 日期列表 df_total = [] # 分日期存的所有df list_total = [] # 每一份word中需要的表数据合集 for d in df.groupby('日期'): dates.append(d[0]) df_total.append(d[1]) for index,date in enumerate(dates): list_oneday = [] # 某一个word所需的表数据 for row in range(len(df_total[index])): list_row = get_table_data(df_total, index, row) # 其中一行数据 list_oneday.append(list_row) list_total.append(list_oneday) for index, date in enumerate(dates): filename = wordname+str(date)+").docx" # 输出的word文件名 title = "("+str(date)[:4]+"."+str(date)[4:6]+"."+str(date)[6:8]+")" # 副标题日期XXXX.XX.XX word = str(date)[:4]+"年"+str(date)[4:6]+"月"+str(date)[6:8]+"日" # 开头、落款日期XXXX年XX月XX日 sentence = get_sentence(df_total, index) # 某一天的文段描述 what_doc(title, word, sentence, list_total[index], filename) #传入需要的内容后输出文档 print(f"文件:{filename} 已保存")
下面让我们分别看看整理表格、整理文段、输出文档是如何实现的。
1、整理表格
获取 excel 表中的一行数据(说明:df_total[df_index]
为一个 dataframe,其 values
为一个二维的 numpy 数组),整理各级指标、各部门报送情况和备注,返回一个列表。
def get_table_data(df_total, df_index, table_row): list1 = df_total[df_index].values[table_row] # excel表中的一行 list2 = list1[3:7] # 一至四级指标 for i in range(len(list2)): # 当前指标为空则沿用上级指标 if list2[i] == '空' and i != 0: list2[i] = list2[i - 1] content = list1[2] + ":\n" + list1[-4] # 报送内容 if '否' in list1[-2]: # 备注 remark = '有记录未上传,' + str(list1[-1]) else: remark = '已上传' list3 = list2.tolist() # 需填入word中的表数据,由numpy数组转为list列表 list3.append(str(content)) list3.append(str(remark)) return list3
2、整理文段
对当日数据中的【填报部门】列中的唯一值计数,得知有 N 个部门填报了数据。对部门分组,获取其相关信息,组合成 [(报送内容,记录数,是否上报,备注)]
的格式,再整理出形如 “有N个部门报送了数据:部门X:“ 报送内容XXX ” X条记录;… …” 的描述串。
def get_sentence(df_total, df_index): df_oneday = df_total[df_index] num = df_oneday['填报部门'].nunique() # 部门的数量 group = [] # 部门名称 detail = [] # 组合某个部门的数据,其中元素为元组格式(, , , ) info = '' # 报送情况描述 for item in df_oneday.groupby('填报部门'): group.append(item[0]) detail.append( list( zip( list(item[1]['报送内容']), list(item[1]['记录数']), list(item[1]['是否上报']), list(item[1]['备注']) ) ) ) for index, g in enumerate(group): # 整理每个部门的填报情况 mes = str(g)+':' # 部门开头 for i in range(len(detail[index])): _mes = detail[index][i] if int(_mes[1])>0: mes = mes + f'“{_mes[0]}”{_mes[1]}条记录;' info = info + mes info = info[:-1]+"。" #将最后一个分号替换成句号 sentence = f"有{num}个部门报送了数据:{info}" return sentence
3、输出文档
(耐心警告!)调整 word 中的文本和表格样式的操作比较繁琐,需一步一步设置,预设表头如下:
table_title = [‘一级指标’, ‘二级指标’, ‘三级指标’, ‘四级指标’, ‘各部门报送情况’, ‘备注’]
其他详见代码注释。
def what_doc(title, word, sentence, table, filename): # 传入副标题日期,开头/落款日期,文段,表数据,文件名 doc = Document() section = doc.sections[0] new_width, new_height = section.page_height, section.page_width section.orientation = WD_ORIENTATION.LANDSCAPE section.page_width = new_width section.page_height = new_height # 段落的全局设置 doc.styles['Normal'].font.name = u'宋体' # 字体 doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体') # 中文字体需再添加这个设置 doc.styles['Normal'].font.size = Pt(14) # 字号 四号对应14 t1 = doc.add_paragraph() # 大标题 t1.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 居中 _t1 = t1.add_run(all_title) _t1.bold = True _t1.font.size = Pt(22) t2 = doc.add_paragraph() # 副标题 t2.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 居中 _t2 = t2.add_run(title + "\n") _t2.bold = True doc.add_paragraph(word + sentence +"\n\n").paragraph_format.first_line_indent = Inches(0.35) # 首行缩进 doc.add_paragraph(word).paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT # 右对齐 doc.add_paragraph("各部门具体报送情况见附件:") doc.add_page_break() # 分页--------------------------------------------------------------- fujian = doc.add_paragraph().add_run("\n附件") fujian.bold = True fujian.font.size = Pt(16) t3 = doc.add_paragraph() # 附件大标题 t3.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 居中 _t3 = t3.add_run("XX公司业务数据表") _t3.bold = True _t3.font.size = Pt(22) rows = len(table)+1 word_table = doc.add_table(rows=rows, cols=6, style='Table Grid') # 创建rows行、6列的表格 word_table.autofit=True # 添加框线 table = [table_title] + table # 固定的表头+表数据 for row in range(rows): # 写入表格 cells = word_table.rows[row].cells for col in range(6): cells[col].text = str(table[row][col]) for i in range(len(word_table.rows)): # 遍历行列,逐格修改样式 for j in range(len(word_table.columns)): for par in word_table.cell(i, j).paragraphs: # 修改字号 for run in par.runs: run.font.size = Pt(10.5) for par in word_table.cell(0, j).paragraphs: # 第一行加粗 for run in par.runs: run.bold = True doc.save(dir+filename)
执行!74份有记录的日报也写好啦,一共178份。

一顿操作猛如虎,总算是批量生成了日报,盒饭该加个鸡腿子了吧… …

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。
有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。
原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!
Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典
