标签归档:转载

同事要我帮忙补写178份Word日报!Python自动化办公实战教程

作者: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实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

实战教程!用 Python 制作抖音爆款视频!

前几天在抖音上刷到一个慢慢变老的视频,播放量居然有 30W+,当时就在想这视频 Python 可不可以做?

经过一番搜索,小编找到了腾讯云的人脸年龄变化 API,上面介绍说只要用户上传一张人脸图片,基于人脸编辑与生成算法,就可以输出一张人脸变老或变年轻的图片,并支持实现人脸不同年龄的变化

准备工作

获取 API 秘钥

第一步,在注册账号之后,打开 API 密钥管理页面(https://console.cloud.tencent.com/cam/capi)获取到 SecretId 和 SecretKey。

第二步,安装腾讯云的 SDK

pip3 install tencentcloud-sdk-python

人脸属性

在人脸年龄变化 API 中有一个 AgeInfo 参数,它包含了 Age 和 FaceRect 两个属性,其中 FaceRect 属性必须填人脸在照片中基于左上角的 X、Y 坐标和人脸的高度与宽度。所以先要调用人脸检测与分析 API 得到这些数据。

下面的示例图是在百度图片中截取的。

import json
import base64
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.iai.v20200303 import iai_client
from tencentcloud.iai.v20200303 import models as models03

sid = "xxx"
skey = "xxx"
try

    filepath = '/Users/imeng/Downloads/face/face.png'
    file = open(filepath, "rb")
    base64_data = base64.b64encode(file.read())

    cred = credential.Credential(sid, skey) 
    httpProfile = HttpProfile()
    httpProfile.endpoint = "iai.tencentcloudapi.com"

    clientProfile = ClientProfile()
    clientProfile.httpProfile = httpProfile
    client = iai_client.IaiClient(cred, "ap-beijing", clientProfile) 

    req = models03.DetectFaceAttributesRequest()
    params = {
        "MaxFaceNum":2,
        "Action":"DetectFace",
        "Version":"2018-03-01",
        "Image": base64_data.decode()
    }
    req.from_json_string(json.dumps(params))
    resp = client.DetectFaceAttributes(req) 

    faceDetailInfos = resp.FaceDetailInfos
    for faceDetailInfo in faceDetailInfos:
        faceRect = faceDetailInfo.FaceRect
        print(faceRect)
except TencentCloudSDKException as err: 
    print(err) 

示例结果

{"X"62"Y"13"Width"145"Height"230}
{"X"426"Y"113"Width"115"Height"139}

修改年龄

在上面已经得到了各个人脸的 X、Y、Width、Height 属性,加上变老的年龄 Age,就可以请求年龄变化 API 了。

这里需要注意的是 models 模块,人脸检测 models 模块是在 tencentcloud.iai.v20200303 包下,人脸年龄变化的 models 是在 tencentcloud.ft.v20200304 下,两个 models 模块并不兼容。

import json
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ft.v20200304 import ft_client, models

cred = credential.Credential(sid, skey) 
httpProfile = HttpProfile()
httpProfile.endpoint = "ft.tencentcloudapi.com"
clientProfile.httpProfile = httpProfile
client = ft_client.FtClient(cred, "ap-beijing", clientProfile) 

req = models.ChangeAgePicRequest()

for age in range(7080):
params = {
    "Image": base64_data.decode(),
    "AgeInfos": [
        {
            "Age": age,
            "FaceRect": {
                "Y": faceDetailInfos[0].FaceRect.Y,
                "X": faceDetailInfos[0].FaceRect.X,
                "Width": faceDetailInfos[0].FaceRect.Width,
                "Height": faceDetailInfos[0].FaceRect.Height
            } 
        },
        {
            "Age": age,
            "FaceRect": {
                "Y": faceDetailInfos[1].FaceRect.Y,
                "X": faceDetailInfos[1].FaceRect.X,
                "Width": faceDetailInfos[1].FaceRect.Width,
                "Height": faceDetailInfos[1].FaceRect.Height
            } 
        }
    ],
    "RspImgType""base64"
}
req.from_json_string(json.dumps(params))
resp = client.ChangeAgePic(req) 
image_base64 = resp.ResultImage
image_data = base64.b64decode(image_base64)
file_path = '/Users/imeng/Downloads/face/{}.png'.format(age)
with open(file_path, 'wb'as f:
    f.write(image_data)
time.sleep(1)

示例结果

最后的视频可以将图片一张一张插入 PPT 幻灯片,点击保存为视频。

转自AirPython.

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

这些 PyCharm 高效操作你知道吗?

熟话说,工欲善其事,必先利其器,PyCharm 作为最好用的 IDE 工具,有着各种各样的骚操作,这是高级开发工程师必须熟悉的基本技能。

pycharm 小技巧是很多的,今天就给大家推荐一些看似简单确非常高效的操作。让你脱离 CV 工程师走向更高一级台阶。

0x01 更换背景图

哈,每天的工作就是面对一堆代码有时候也是会觉得挺烦心的是吧,特别是找 bug 找好久依旧定位不到问题的时候,这时候就体现出男女搭配干活不累的好处了,通过更换 IDE 的背景图让你的程序员鼓励师每天不重样。

设置路径如下:Preferences -> Appearance -> Background Image

至于去哪里找程序员鼓励师的高清图片,我相信你们知道的肯定比我多,对吧。

0x02 打开最近打开的文件

有时候打开的文件实在是太多了,tab 栏就会被占满,想定位到自己想要的文件还是很麻烦的,这时候这个快捷键操作就派上用场了,「command + E」,可以直接打开自己最近编辑过的文件,方便快捷,按两下有惊喜哦。

0x03 查看源码

有时候代码一多,看起来很乱,根本不晓得这个变量是在哪里定义的,用来干嘛的,这时候就可以通过「command + B」来快速定位到定义变量的位置,亦或者在将光标定位到函数位置,按「command + B」可以直达函数的内部实现,看开源代码时特别方便。

0x04 开启新的一行

写程序我们经常需要换行,这都是常规操作,如果你还是先将光标定位到行尾或者行首,之后按 Enter 键来换行,效率就有点低了。如论你的光标在一行的哪个位置,都可以通过「shift enter」在本行后面开启新的一行,通过「command + option + enter」在本行前面开启新的一行。

0x05 快速返回

这个我一般会搭配「command + E」来一起使用,如果你想去的文件是刚编辑过的,那么可以直接使用 「command + option + 方向箭」来快速跳转到刚才编辑的位置,不仅仅局限于不同文件哦,在同一个文件内也是可以的。

0x06 查找类/文件

当你想快速搜索一个类或者文件时,可以双击 shift,直接输入你要搜索的内容即可。同时如果你确定搜索的是类的话,那么可以通过「command + o」来开启精准搜索,如果搜索的是文件,那么可以通过「command + shift + o」精准搜索。

0x07 自动清除没有引用的包

有时候看到那些变灰色的 import 很是烦人,难道只能一个一个删除么,当然不是,直接 「command + option + o」即可一键清除。

总结

今天给大家普及了一些 PyCharm 的高效操作,希望对小伙伴们的工作和学习有所帮助。