作者: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 pd
import xlrd
from docx import Document
from docx.shared import Pt
from docx.shared import Inches
from docx.oxml.ns import qn
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from 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实用宝典