用Python批量生成信函

这篇博文里,我将介绍利用Python编程语言自动化一些繁琐机械的工作的一个实际例子。适合有一点编程基础的财务人员参考。

最近工作上有一个需求:有一个业务上的Excel表格,里面有几千行人员数据,并且有所属企业等其他信息列。任务是给表格里每一个企业发送一封信,里面列出下属人员的相关信息并通知整改。

首先我想到Word上有一个功能叫做”邮件“,里面可以做一个邮件模板,然后挖空一些地方(比如抬头、称呼、落款)。Word会根据你提供的数据源表格,自动地填充这些地方,批量生成大量信件。

但是!这个功能很弱,没法合并同一个公司的员工信息。虽然也不是不能通过“域”语法实现,但感觉很麻烦,这种数据处理的事情还是搬出Python吧。

任务可以拆分成三块:

①读取Excel表格里的人员数据

②把每一行人员数据追加到该公司的信函中

③渲染信函,生成美观可读文件(用于电邮寄送,或者打印)。

总体思路是:先读取excel文件,按格式要求做出markdown格式的信函,然后用pandoc转换成html。

这里介绍一下pandoc,pandoc是一个在不同标记文档格式之间进行转换的命令行工具,支持html/pdf/docx/latex/markdown等。我们可以在Python中调用pandoc,来实现格式转换。

直接上代码了,首先用xlrd库读取表格。

1
2
3
4
5
6
import xlrd
excel =r'list.xlsx' #指定数据源文件
output_path=os.path.join(sys.path[0],"output")#输出目录
workbook = xlrd.open_workbook(excel)
print(workbook.sheet_names())
sheet1 = workbook.sheet_by_index(0)

然后抽取每一条数据,生成markdown格式的信函。

1
2
3
4
5
6
7
8
9
10
11
for i in range(1,sheet1.nrows-1):
try:
md_company=sheet1.cell(i,2).value.strip()
md_file='%s.md'%(md_company)
md_fullfile=os.path.join(output_path,md_file)
print("准备写入文件:",md_fullfile)
if os.path.exists(md_fullfile)==False: #不存在文件的话,创建并写入信函头和表头
...
except Exception as e:
print(e)
continue

由于markdown支持html标签,我们可以逐行这样准备要写入markdown的数据。记得用换行符。

1
2
3
4
contenthead="<br/><br/><center><h2>告知函</h2></center>\n\n"
contenthead=contenthead+"尊敬的用户,……\n"
contenthead=contenthead+"\n\n姓名 | 个人账号 | 证件号 | 账户状态 | 错误原因\n"
contenthead=contenthead+":-: | :-: | :-: | :-: | :-:\n"

然后写入文件。这里用到codecs来指定编码为utf8,避免转换期间的编码错误。

1
2
3
4
5
6
7
8
9
10
if os.path.exists(md_fullfile)==False
...
file = codecs.open(md_fullfile, 'a','utf-8')
file.write(contenthead) #先写头
writeline(sheet1,i,file)
file.close()
else: #存在文件的话,就接着追加人员数据
file=codecs.open(md_fullfile,'a','utf-8')
writeline(sheet1,i,file)
file.close()

这里的writeline()函数,就是逐行往信函markdown文件里写员工信息。记得数据要拼接成markdown可识别的表格行格式,用 dash符分隔,像这样。

(我一直觉得自己写代码一点都不objective,像写过程化语句)

1
2
3
4
5
def writeline(sheet,i,file)
file.write(sheet.cell(i, 4).value+" | ")#姓名E
file.write(sheet.cell(i, 3).value+" | ")#账号D
file.write(sheet.cell(i, 5).value+" | ")#证件号F
file.write(sheet.cell(i, 9).value+" | ")#账户状态J

最后一步,就是在所有数据都追加完毕后,给所有的信函markdown文件追加落款,并顺便转换成html格式。这里用os.walk()来遍历。很简单,网上的代码段一大把。这里可以用div的style标记来做落款的右对齐,并且通过datetime模块取生成时间作为落款时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
def write_prefix():
for root,dirs,files in os.walk(output_path):
for fullfiles in files:
fullfile = os.path.join(root, fullfiles).lower()
extension = os.path.splitext(fullfile)[1][1:]
if extension in ["md","txt"]:
file=codecs.open(fullfile,'a','utf-8')
file.write("  特此告知!\n\n\n") #用全角空格控制缩进
file.write("<div style=\"text-align: right\">XX市XX主管部门 </div>\n")
file.write("<div style=\"text-align: right\">%s年%s月%s日</div>\n" %\
(datetime.datetime.now().year,datetime.datetime.now().month,datetime.datetime.now().day))
file.close()
run_pandoc(fullfile,fullfile.replace(".md",".html"))

上面代码段的最后一行调用了run_pandoc(),就是调用pandoc命令来转换markdown到html。为什么我不转换成docx?因为这玩意要依赖latex库,而且样式支持奇差。假如是用于打印或网络传输的信函,html足够了。

pandoc可以在转换时附带一个css,让转换出来的信件样式更美观。css可以网上挑一个漂亮的。记得加上–self-contained函数,把css内联到html里。

1
2
3
4
def run_pandoc(source_file,object_file): 
print("正在渲染",object_file)
if ".htm" in object_file:
os.system("pandoc --css style.css -s %s -o %s --self-contained --metadata pagetitle=\" \""%(source_file,object_file))

这样就好了。大概十秒钟左右就能生成五百份信函。性能也说得过去,没必要上异步文件处理库(aiofile之类)。

最后欢呼一下:科技解放生产力。

对这方面知识感兴趣但对编程知之甚少的网友,可以参考一下这本书:《Python编程快速上手:让繁琐工作自动化》