神器Jinja2,用 Python 快速生成分析报告!
01
前言
大家好,我是宝器。
在之前的文章中,我们使用 Python 开发了一个简单的基金购买策略的回测系统。在代码执行完毕后,会生成一系列的结果,包含大量图片、表格如下
此时如果一个一个查看的话便十分低效,如果能使用一个文件把全部输出结果都保存将会大大提高体验。
首先想到的当然是 PDF
格式,利用 Python 操作 PDF 也是之前文章分享过很多,想必利用表格+图片生成一个新的PDF并不困难。研究了一番后,发现确实不难,但是太繁琐了,并且 PDF 涉及格式、分页等,如果没有调整好可能会将一张图片放在两页或者一页只有一张图其余全是空白,十分影响美观,由于我每次产生的结果并不固定,因此很难找到一个通用的模版,遂放弃。
PDF 既然不行,Word就更不用考虑了,所以只能选择 html
格式,虽然跨平台性没有 PDF 好,但是胜在排版简单,不需要考虑分页处理。基于 Python 生成 html 有很多成熟的 web 开发框架可以选择,但为了整体过程不太复杂,最终选择 Jinja2
来实现这个需求。
02
jinja2
Jinja2 是一个 Python 的功能齐全的模板引擎,简单来说就是我们将 html 的主要部分写好,将需要填充的内容空出来,这样就是一个模版,之后就可以使用 Jinja2 来自动将模版文件填充,形成一个完整的 html 文件。
填入文字
首先我们需要制作一个最简单的模版文件template.html
,内容如下
<html>
<head>
<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>
<title>report</title>
</head>
<h1>基金策略回测报告</h1>
<body>
<h2>一、策略详情</h2>
<p>策略描述:{{ strategy_name }}</p>
<p>回测时间段:{{ start_time }} --> {{ end_time }} </p>
<p>初始本金:{{ money }}</p>
</body>
</html>
直接打开如下
其中被 {{}}
包起来的变量,就是我们需要用 Python 传递给模版的,很明显上方代码缺少四个变量,下面是 Python 代码
import pandas as pdfrom jinja2 import Environment, FileSystemLoader
data = {'strategy_name': '第一个策略', 'start_time': '2020-01-01', 'end_time': '2021-06-01', 'money': 20000}
env = Environment(loader=FileSystemLoader('./'))
template = env.get_template('template.html')
with open('out.html', 'w+', encoding='utf-8') as f: out = template.render(strategy_name=data['strategy_name'], start_time=data['start_time'], end_time=data['end_time'], money=data['money']) f.write(out) f.close()
在上面的代码中,我们使用env.get_template('template.html')
读取模版文件,并将需要需要传入的数据写入字典中,并将 value
通过template.render
传给模版并渲染输出,现在打开生成的 out.html 内容如下
可以看到目标位置的文字都被正确填充,当然填入文字是最基本的操作,下面继续介绍如何自动创建表格。
填入表格
其实填入表格和填入文字本质上是一样的,只不过需要使用循环来操作,例如将回测指标汇总.xlsx
添加到html中
首先在模版文件中创建一个表格
<h2>二、回测结果</h2>
<table border='1' width = '40%' cellspacing='0' cellpadding='0'>
<tr>
<th>基金名称</th>
<th>总投入本金</th>
<th>总收益率</th>
<th>最大回测率</th>
</tr>
{% for item in items %}
<tr align='center'>
<td>{{ item.基金名称 }}</td>
<td>{{ item.消耗本金 }}</td>
<td>{{ item.总收益率 }}</td>
<td>{{ item.最大回撤率 }}</td>
</tr>
{% endfor%}
</table>
在上面的代码中,我们使用创建了一个table,并将表头写好,将 cell 内容设置为待填充,注意在jinja2中的循环是通过{% %}
来完成的。
例如{% for item in items %}
就需要我们传入一个字典形式 items ,并且里面包含基金名称、消耗本金、总收益率、最大回撤率四个key,此时 Python 代码修改如下
import pandas as pdfrom jinja2 import Environment, FileSystemLoader
df = pd.read_excel('回测指标汇总.xlsx')df['消耗本金'] = df['消耗本金'].astype(str) + ' 元'df['最大回撤率'] = df['最大回撤率'].astype(str) + '%'df['总收益率'] = df['总收益率'].astype(str) + '%'data = df.to_dict('records')
results = {}results.update({'strategy_name': '第一个策略', 'start_time': '2020-01-01', 'end_time': '2021-06-01', 'money': 20000, 'items': data})
env = Environment(loader=FileSystemLoader('./'))
template = env.get_template('template.html')
with open('out.html', 'w+', encoding='utf-8') as f: out = template.render(strategy_name=results['strategy_name'], start_time=results['start_time'], end_time=results['end_time'], money=results['money'], items = results['items']) f.write(out) f.close()
需要注意的是因为我们需要循环操作,所以传入的 items 就需要是可迭代的,但又需要根据关键字匹配值,所以可以将每一行数据转换为字典,并放在 list 中,这一步可以使用 pandas 读取excel并直接使用df.to_dict('records')
完成(吹一波pandas
)
现在执行上面的代码,并打开输出后的 html
可以看到,Excel的全部内容都被正确的填入,下面介绍如何填入图片。
填入图片
其实 html 展示图片就是通过<a>
标签超链接指向图片地址,所以本质上和填充文字差不多,只需要在模版文件中把本地图片地址预留出来,之后使用 jinja2 传入即可,例如可以在模版中添加以下内容
<a name='{{ 回测指标 }}'> <img src='{{ indicator }}' width='850'></a>
此时在 Python 代码中指定indicator图片路径并传入即可
indicator = 'images/' + '回测指标.png'with open('out.html', 'w+', encoding='utf-8') as f: out = template.render(strategy_name=results['strategy_name'], start_time=results['start_time'], end_time=results['end_time'], money=results['money'], items = results['items'], indicator = indicator) f.write(out) f.close()
执行后即可正确展示图片如下
当然我们的图片不止一张,可以像上面插入表格一样将图片地址循环填入即可,本文不再赘述,感兴趣读者可以自己研究。
修改样式
当然,默认生成的 html 样式可能不够好看,我们也可以进一步添加一点CSS代码来调整,为了简化可以直接将代码写在标签中
<style type='text/css'>
h1 {margin-left: 20px}
h2 {margin-left: 20px;
font-size: 19px;
font-weight: bold;
display: inline-block;
padding-left: 10px;
border-left: 5px solid #916dd5;}
h3 {margin-left: 20px}
h4 {margin-left: 20px;
margin-bottom: -5px}
table {margin-left: 20px;
margin-top: 5px;
margin-bottom: 5px}
p {margin-left: 20px}
a {margin-top: -5px;}
</style>
主要是添加了一点缩进和间距,让结果看起来更舒服一点,现在自动生成的 html 文件部分如下
最后只需要将上述 Python 代码封装,然后在主函数运行结束后自动将相关参数进行传递,即可全程自动化生成 html
报告!
当然本文介绍的 Jinja2 操作只是其简单的一个应用,更多的用法与功能感兴趣的读者可以自己查阅官方文档。