你很可能也看到过公众号文章下方的关于 Python 自动化生成报表的广告,因为我自己就经常看到,说的是一个人因为报表做不出来,一筹莫展,马上要被辞职了,这时一个高手拿过电脑,一顿操作猛如虎,然后一份精美的报表就生成了,被帮助的人写满一脸的崇拜。
其实不用崇拜,很简单,今天,我来告诉你方法。
这里说做报表,不用 excel,不用专业的报表平台,就是纯 html 生成一些漂亮的可视化报表,甚至可以交互,这样的报表你可以发 html 邮件给老板,老板不需要下载,不需要登陆专业的报表平台,打开邮件就可以直接看到,还可以点击来交互,非常方便,如果你这样做,那离升职加薪不会太远👍。
废话不多说,现在就告诉你方法。咱们以目标为导向,技术不技术的不重要,实现了就好。
选择一个报表模版
首先你要生成什么样的报表,这里有个网站可以供你选择,就是大名鼎鼎的 ECharts 库,https://echarts.apache.org/zh/index.html
点击所有示例,可以看到很多样例图片:
选择一个你想用的,比如,我们选择「柱状图」->「基础柱状图」
点击「下载示例」,就可以下载一个 html 文件,用浏览器打开就是这个柱状图。
现在用个编辑器打开这个 html 文件,修改其中的数据,我们就可以生成一个属于自己的报表
然后保存,这样一个报表就生成了,如果简陋一点的话,你直接把这个 html 作为邮件的附件发给老板,老板双击这个 html 文件就可以在浏览器上看到,不过这并不是完美的,万一老板的电脑没有浏览器呢。
完美的解决方案是将这些报表显示在邮件的正文。
用 Python 发送 html 邮件
这个前文最简单的方式发送邮件,让程序出错自动发邮件 小节 “发送多彩的 html 邮件” 有说道,这里就不多说了。
使用 Jinja2 来渲染 html
第一步中的替换是手工操作的,假如数据量比较大,可能就没那么方便,这不,我们有万能的 Python 嘛。
如果用过 Django,你就知道 Jinja2 的模版大法,简单来讲,Jinja2 讲一个文件中的标识替换成你需要的内容。这里我们用的正是这一点。
比如 html 文件中的有这么一段:
option = {
xAxis: {
type: 'category',
data: ['张三', '李四', '王五', '赵六']
},
yAxis: {
type: 'value'
},
series: [{
data: [120, 200, 150, 80],
type: 'bar'
}]
};
我们希望替换其中两处的 data,就可以先这样写
option = {
xAxis: {
type: 'category',
data: {{ data1 }}
},
yAxis: {
type: 'value'
},
series: [{
data: {{ data2 }},
type: 'bar'
}]
};
然后借助 Jinja2 可以很方便的替换:
from templater import DefaultTemplater
if __name__ == "__main__":
templater = DefaultTemplater("bar-simple.html", "bar-simple-templeted.html")
data1 = ['张三1', '李四2', '王五3', '赵六4']
data2 = [3120, 3200, 3150, 980]
tags = {
"data1": data1,
"data2": data2,
}
templater.render(tags)
打开 bar-simple-templeted.html 发现已经被替换掉了,在批量制作报表时,是不是很方便?
这里用到了 DefaultTemplater,其实现代码如下 (templater.py):
# templater.py
from dataclasses import dataclass
from typing import Dict
from jinja2 import Template
@dataclass
class DefaultTemplater(object):
""" Allow to inject data in a jinja2 templated file and write the result to specified destination """
source: str
destination: str
def render(self, data: Dict) -> None:
""" Write template from source filled with data to destination
Args:
data: the data to inject in the template
"""
self.load_template()
filled_template = self.replace(data)
self.write_filled_template(filled_template)
def load_template(self) -> None:
""" Load template from source
"""
with open(self.source, "r") as f:
self.template = f.read()
def replace(self, values: Dict) -> str:
""" Replace tag in template with values
Args:
values: dict with key: tag to search in template, value: value to replace the tag
"""
template = Template(self.template)
templated = template.render(**values)
return templated
def write_filled_template(self, content: str):
"""Write the result of the template and injected value to destination
Args:
content: what to write
"""
with open(self.destination, "w") as f:
f.write(content)
比如说一些更复杂的
其 html 代码如下(与 echarts 上下载的略有调整):
<!DOCTYPE html>
<html style="height: 100%">
<head>
<meta charset="utf-8">
</head>
<body style="height: 100%; margin: 0">
<div id="container" style="height: 100%"></div>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script type="text/javascript">
var dom = document.getElementById("container");
var myChart = echarts.init(dom);
var app = {};
var option;
var data_list = [
[1, 2, 3, 4, 5],
[8, 4, 3, 2, 3],
[5, 9, 6, 10, 3],
[9, 7, 4, 7, 6],
[5, 4, 3, 2, 9]
]
option = {
xAxis: {
max: 'dataMax',
},
yAxis: {
type: 'category',
data: ['A', 'B', 'C', 'D', 'E'],
inverse: true,
animationDuration: 300,
animationDurationUpdate: 300,
max: 4 // only the largest 3 bars will be displayed
},
series: [{
realtimeSort: true,
name: 'X',
type: 'bar',
data: data_list[0],
label: {
show: true,
position: 'right',
valueAnimation: true
}
}],
legend: {
show: true
},
animationDuration: 0,
animationDurationUpdate: 3000,
animationEasing: 'linear',
animationEasingUpdate: 'linear'
};
var index = 1;
function run () {
var data = option.series[0].data;
if(index >= data_list.length){
return;
}
for (var i = 0; i < data.length; ++i) {
data[i] = data_list[index][i];
}
index++;
myChart.setOption(option);
}
setInterval(function () {
run();
}, 3000);
if (option && typeof option === 'object') {
myChart.setOption(option);
}
</script>
</body>
</html>
你只需要修改 data_list 和 yAxis 的分类就可以实现自己想要的动态报表。最好懂一点 javascript 的语法,这样可以改一改数据的结构,更方便的生成自己想要的报表。
最后的话
要生成报表,其实并不需要太精通技术,从网上下载个模版,自己改改数据,改改分类,就可以制作一个 html 报表,然后可以将 html 作为邮件正文发送出去,也可以生成图片,pdf 发送,具体场景就看自己需求了。如果要批量制作很多同类报表,可以借助 Jinja2 的模版大法,批量替换报表中的数据,更高效的完成。