你很可能也看到过公众号文章下方的关于 Python 自动化生成报表的广告,因为我自己就经常看到,说的是一个人因为报表做不出来,一筹莫展,马上要被辞职了,这时一个高手拿过电脑,一顿操作猛如虎,然后一份精美的报表就生成了,被帮助的人写满一脸的崇拜。

其实不用崇拜,很简单,今天,我来告诉你方法。

这里说做报表,不用 excel,不用专业的报表平台,就是纯 html 生成一些漂亮的可视化报表,甚至可以交互,这样的报表你可以发 html 邮件给老板,老板不需要下载,不需要登陆专业的报表平台,打开邮件就可以直接看到,还可以点击来交互,非常方便,如果你这样做,那离升职加薪不会太远👍。

废话不多说,现在就告诉你方法。咱们以目标为导向,技术不技术的不重要,实现了就好。

选择一个报表模版

首先你要生成什么样的报表,这里有个网站可以供你选择,就是大名鼎鼎的 ECharts 库,https://echarts.apache.org/zh/index.html

玩转报表 - 图1

点击所有示例,可以看到很多样例图片:

玩转报表 - 图2

选择一个你想用的,比如,我们选择「柱状图」->「基础柱状图」

玩转报表 - 图3

点击「下载示例」,就可以下载一个 html 文件,用浏览器打开就是这个柱状图。

玩转报表 - 图4

现在用个编辑器打开这个 html 文件,修改其中的数据,我们就可以生成一个属于自己的报表

玩转报表 - 图5

然后保存,这样一个报表就生成了,如果简陋一点的话,你直接把这个 html 作为邮件的附件发给老板,老板双击这个 html 文件就可以在浏览器上看到,不过这并不是完美的,万一老板的电脑没有浏览器呢。

完美的解决方案是将这些报表显示在邮件的正文。

用 Python 发送 html 邮件

这个前文最简单的方式发送邮件,让程序出错自动发邮件 小节 “发送多彩的 html 邮件” 有说道,这里就不多说了。

使用 Jinja2 来渲染 html

第一步中的替换是手工操作的,假如数据量比较大,可能就没那么方便,这不,我们有万能的 Python 嘛。

如果用过 Django,你就知道 Jinja2 的模版大法,简单来讲,Jinja2 讲一个文件中的标识替换成你需要的内容。这里我们用的正是这一点。

比如 html 文件中的有这么一段:

  1. option = {
  2. xAxis: {
  3. type: 'category',
  4. data: ['张三', '李四', '王五', '赵六']
  5. },
  6. yAxis: {
  7. type: 'value'
  8. },
  9. series: [{
  10. data: [120, 200, 150, 80],
  11. type: 'bar'
  12. }]
  13. };

我们希望替换其中两处的 data,就可以先这样写

  1. option = {
  2. xAxis: {
  3. type: 'category',
  4. data: {{ data1 }}
  5. },
  6. yAxis: {
  7. type: 'value'
  8. },
  9. series: [{
  10. data: {{ data2 }},
  11. type: 'bar'
  12. }]
  13. };

然后借助 Jinja2 可以很方便的替换:

  1. from templater import DefaultTemplater
  2. if __name__ == "__main__":
  3. templater = DefaultTemplater("bar-simple.html", "bar-simple-templeted.html")
  4. data1 = ['张三1', '李四2', '王五3', '赵六4']
  5. data2 = [3120, 3200, 3150, 980]
  6. tags = {
  7. "data1": data1,
  8. "data2": data2,
  9. }
  10. templater.render(tags)

打开 bar-simple-templeted.html 发现已经被替换掉了,在批量制作报表时,是不是很方便?

玩转报表 - 图6

这里用到了 DefaultTemplater,其实现代码如下 (templater.py):

  1. # templater.py
  2. from dataclasses import dataclass
  3. from typing import Dict
  4. from jinja2 import Template
  5. @dataclass
  6. class DefaultTemplater(object):
  7. """ Allow to inject data in a jinja2 templated file and write the result to specified destination """
  8. source: str
  9. destination: str
  10. def render(self, data: Dict) -> None:
  11. """ Write template from source filled with data to destination
  12. Args:
  13. data: the data to inject in the template
  14. """
  15. self.load_template()
  16. filled_template = self.replace(data)
  17. self.write_filled_template(filled_template)
  18. def load_template(self) -> None:
  19. """ Load template from source
  20. """
  21. with open(self.source, "r") as f:
  22. self.template = f.read()
  23. def replace(self, values: Dict) -> str:
  24. """ Replace tag in template with values
  25. Args:
  26. values: dict with key: tag to search in template, value: value to replace the tag
  27. """
  28. template = Template(self.template)
  29. templated = template.render(**values)
  30. return templated
  31. def write_filled_template(self, content: str):
  32. """Write the result of the template and injected value to destination
  33. Args:
  34. content: what to write
  35. """
  36. with open(self.destination, "w") as f:
  37. f.write(content)

比如说一些更复杂的

其 html 代码如下(与 echarts 上下载的略有调整):

  1. <!DOCTYPE html>
  2. <html style="height: 100%">
  3. <head>
  4. <meta charset="utf-8">
  5. </head>
  6. <body style="height: 100%; margin: 0">
  7. <div id="container" style="height: 100%"></div>
  8. <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
  9. <script type="text/javascript">
  10. var dom = document.getElementById("container");
  11. var myChart = echarts.init(dom);
  12. var app = {};
  13. var option;
  14. var data_list = [
  15. [1, 2, 3, 4, 5],
  16. [8, 4, 3, 2, 3],
  17. [5, 9, 6, 10, 3],
  18. [9, 7, 4, 7, 6],
  19. [5, 4, 3, 2, 9]
  20. ]
  21. option = {
  22. xAxis: {
  23. max: 'dataMax',
  24. },
  25. yAxis: {
  26. type: 'category',
  27. data: ['A', 'B', 'C', 'D', 'E'],
  28. inverse: true,
  29. animationDuration: 300,
  30. animationDurationUpdate: 300,
  31. max: 4 // only the largest 3 bars will be displayed
  32. },
  33. series: [{
  34. realtimeSort: true,
  35. name: 'X',
  36. type: 'bar',
  37. data: data_list[0],
  38. label: {
  39. show: true,
  40. position: 'right',
  41. valueAnimation: true
  42. }
  43. }],
  44. legend: {
  45. show: true
  46. },
  47. animationDuration: 0,
  48. animationDurationUpdate: 3000,
  49. animationEasing: 'linear',
  50. animationEasingUpdate: 'linear'
  51. };
  52. var index = 1;
  53. function run () {
  54. var data = option.series[0].data;
  55. if(index >= data_list.length){
  56. return;
  57. }
  58. for (var i = 0; i < data.length; ++i) {
  59. data[i] = data_list[index][i];
  60. }
  61. index++;
  62. myChart.setOption(option);
  63. }
  64. setInterval(function () {
  65. run();
  66. }, 3000);
  67. if (option && typeof option === 'object') {
  68. myChart.setOption(option);
  69. }
  70. </script>
  71. </body>
  72. </html>

你只需要修改 data_list 和 yAxis 的分类就可以实现自己想要的动态报表。最好懂一点 javascript 的语法,这样可以改一改数据的结构,更方便的生成自己想要的报表。

最后的话

要生成报表,其实并不需要太精通技术,从网上下载个模版,自己改改数据,改改分类,就可以制作一个 html 报表,然后可以将 html 作为邮件正文发送出去,也可以生成图片,pdf 发送,具体场景就看自己需求了。如果要批量制作很多同类报表,可以借助 Jinja2 的模版大法,批量替换报表中的数据,更高效的完成。