在实际工作中,我们常常会遇到定时发送邮件的任务,基于我的实践,分享给大家,也许一篇文章写不完,就先列个目录。
本文想要解决的问题:
- 用python构造一封邮件,并设置定时发送出去。往往,这只是最低级的需求,实际工作中会有各种细节和附加条件。
- 学会构造文本、HTML两种格式的邮件正文,学会构造和添加附件,其中,HTML格式要学会使用超链接,学会添加CSS用来美化正文。
- 将Dataframe格式的表格直接转化为HTML格式的表格,如果有超链接,要学会更改超链接,使之能完全显示,并在HTML正文显示对应的连接内容(比如显示连接的图片)。
1. 初步学会使用python编写发送邮件的脚本
关于python发邮件的类似文章有很多,不过,作为初学者,依然有许多需要注意的细节,如果作者没有写清楚的话,是很容易让人抓狂的。
由于2020年官方就停止更新和支持python2,所以本文顺应大势,使用的python3.6
1. python库
- email库,用来编辑邮件内容的,包括标题,发件人,接收人,正文等。
- smtplib库,用来发送邮件的,包括创建邮件服务,登陆,发送,退出等动作
一般来说,我是做如下导入,每一个模块都有相应的说明,具体应用继续看后面的内容。
# 导入相关库-email
from email.mime.multipart import MIMEMultipart # 构建邮件头信息,包括发件人,接收人,标题等
from email.mime.text import MIMEText # 构建邮件正文,可以是text,也可以是HTML
from email.mime.application import MIMEApplication # 构建邮件附件,理论上,只要是文件即可,一般是图片,Excel表格,word文件等
from email.header import Header # 专门构建邮件标题的,这样做,可以支持标题中文
import smtplib
2. 发送一个最简单的hello word的邮件(有重点)
-
发件人信息(重点)
这一小节的重点,也是很多小白容易犯糊涂的地方,就是发件人的账号和密码,以及相应的邮件服务器设置。不同的邮箱,其规则完全不一样,如果你有企业邮箱,那最好用企业邮箱,这点要记住,因为个人邮箱很容易出现网络问题、触发反垃圾机制等等,这是实践的出来的经验。另外,如果你使用过Foxmail、outlook等第三方邮件服务器,那你就更容易理解了。
上面箭头所指,就是163个人邮箱的发件服务器:smtp.163.com,如果是163的企业邮箱,其服务器地址则是:smtp.ym.163.com,我就是用的这个。
下面我要将的内容很关键:163个人邮箱的密码,不是登陆密码,而是客户端授权码,如下图所示:
我们这里是属于使用SMTP服务登陆和发送邮件的,所以使用常规的登陆密码,是无法发送邮件的。
然而,163的企业邮箱,却没有这个选项,直接使用登陆密码就可以发送邮件(我一直没明白为什么不一样,也许企业邮箱默认的就是开通了这些服务的,给我们的密码也即是授权码)。
常见的qq邮箱,谷歌邮箱,雅虎邮箱都是有这些区别的,新手一定要注意。下面就是我发送邮件的账号的密码设置,大家可以参照一下,不懂的直接在评论区@我:
# 邮件服务信息,个人
# smtp_server = 'smtp.163.com'
# username = "lihua.0221@163.com"
# password = 'xxxxxx' # 授权码,并不是邮箱登陆密码
# 邮件服务信息,公司
smtp_server = 'smtp.ym.163.com'
username = "lihua@everimaging.com"
password = 'xxxxxxxxx' # 授权码,企业163的就是登陆密码
这些信息,我们在构建邮件的发件人的时候需要用到,在使用SMTP发送邮件的时候也会用到,所以,一开始就给出来。
- 构建邮件正文
# 邮件发送和接收人
sender = username
receiver = ['lihua@everimaging.com', '724694053@qq.com']
# 邮件头信息
msg = MIMEMultipart('related')
msg['Subject'] = Header("我的第一封python邮件")
msg["From"] = sender
msg['To'] = ','.join(receiver) # 这里要注意
# text 内容
content_text = MIMEText("Hello World", "text", "utf-8")
msg.attach(content_text)
可以看到,这一节没有什么难点,先是定义发送者和接收人,然后使用MIMEMultipart类构建一个消息体msg,然后定义msg中的主题,发件人,接收人。其中,主题使用了Header类封装,目的是为了支持中文,最后,添加一段text的正文“hello world”,使用的是MIMEText类封装,第一个参数代表内容,第二个参数代表类型是text,另外还有html类型可选,下节介绍,最后一个参数是定义编码。
这里面只有一个地方需要注意,那就是msg['To'] = ','.join(receiver)这里,我们的邮件接收人是可以很多人的(列表),但是还需要用逗号把它们连接成一个字符串(email库的bug),如果是直接将列表扔给它,是要出错的。
- 发送邮件
# 发送邮件,测试成功,流程都是固定的:创建客户端,登陆,发送,关闭
email_client = smtplib.SMTP(smtp_server)
email_client.login(username, password)
email_client.sendmail(sender, receiver, msg.as_string())
email_client.quit()
这段代码,先是使用前面定义的邮件发送服务器:smtp_server = 'smtp.ym.163.com'来创建一个SMTP服务,然后传入账号和密码执行登陆,然后是发送邮件,需要的参数有:发送人,接收人,消息体,其中消息体执行了as_string(),将整个msg对象转化为了字符串,最后是退出服务。
这样,我们的第一封邮件就发送成功了。下面是这一阶段的完整代码,我将自己账号的password隐藏了,你只需要将你的账号信息替换进去,执行即可,如果没有成功,出现了任何不能解决的问题,请在评论区@我。
# 导入相关库-email
from email.mime.multipart import MIMEMultipart # 构建邮件头信息,包括发件人,接收人,标题等
from email.mime.text import MIMEText # 构建邮件正文,可以是text,也可以是HTML
from email.mime.application import MIMEApplication # 构建邮件附件,理论上,只要是文件即可,一般是图片,Excel表格,word文件等
from email.header import Header # 专门构建邮件标题的,这样做,可以支持标题中文
import smtplib
def send_email():
"""发送邮件的脚本"""
# 邮件服务信息,个人
# smtp_server = 'smtp.163.com'
# username = "lihua.0221@163.com"
# password = 'xxxxx' # 授权码,并不是邮箱登陆密码
# 邮件服务信息,公司
smtp_server = 'smtp.ym.163.com'
username = "lihua@everimaging.com"
password = 'xxxxx' # 授权码,企业163的就是登陆密码
# 邮件发送和接收人
sender = username
receiver = ['lihua@everimaging.com', '724694053@qq.com']
# 邮件头信息
msg = MIMEMultipart('related')
msg['Subject'] = Header("我的第一封python邮件")
msg["From"] = sender
msg['To'] = ','.join(receiver) # 这里要注意
# text 内容
content_text = MIMEText("Hello World", "text", "utf-8")
msg.attach(content_text)
# 发送邮件,测试成功,流程都是固定的:创建客户端,登陆,发送,关闭
email_client = smtplib.SMTP(smtp_server)
email_client.login(username, password)
email_client.sendmail(sender, receiver, msg.as_string())
email_client.quit()
if __name__ == '__main__':
send_email()
2. 学会构建HTML格式的正文,添加附件
前面我们已经学会了基本的邮件发送脚本编写,正文使用的是text,但是,我们常见的邮件基本都是以HTML格式作为正文的,偶尔还会带上一些附件。
-
HTML
HTML就是网页,使用这种格式的正文,可以使我们的邮件内容变得丰富无比,理论上,网页可以做成什么样,我们HTML格式的邮件正文就可以做成什么样!!
比如,我有下面这样一个网页:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是一封python写的邮件,使用的是HTML格式构造正文</h1>
<h2>可以为文字添加超链接,比如:<a href="//www.greatytc.com/u/8159970c6959">简书-小溏</a></h2>
<hr>
<h3>还可以添加图片,比如下面这张</h3>
<img src="https://pub-static.haozhaopian.net/assets/projects/export/jpg/29736970-a991-4b91-91af-854a8eb561e6.jpg">
</body>
</html>
在浏览器中显示如下:
现在,我将它放进我的邮件正文中,使用HTML格式,这种方式可以为文字添加超链接,可以插入图片
# 定义一个字符串,内容就是HTML代码
html_msg = \
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是一封python写的邮件,使用的是HTML格式构造正文</h1>
<h2>可以为文字添加超链接,比如:<a href="//www.greatytc.com/u/8159970c6959">简书-小溏</a></h2>
<hr>
<h3>还可以添加图片,比如下面这张</h3>
<img src="https://pub-static.haozhaopian.net/assets/projects/export/jpg/29736970-a991-4b91-91af-854a8eb561e6.jpg">
</body>
</html>
"""
# html 内容
content_html = MIMEText(html_msg, "html", "utf-8")
msg.attach(content_html)
我们定义好HTML字符串,然后使用MIMEText类封装它,第二个参数写“html”,同样添加(attach)到msg对象中。这和我们前面添加“hello word”这种纯text的内容方式是一样的,如果你现在还不明白上面这段代码该放在哪个位置,稍后我会给出完整的代码。
-
添加附件
关于附件,有2个问题需要重视: - 附件的路径,最好是使用全路径。
- 附件的中文名称,需要设置编码。
添加附件前,我们需要确定附件的位置,也就是在系统上的路径。关于路径,我是吃过大亏的,所以各位新手也要注意,在不同的系统(linux和windows)是不一样的路径,分隔符也有区别,特别是要定时跑的脚本,在linux系统中的不同位置运行该脚本,相对路径都不一样,稍不注意就会break掉。
- 附件的中文名称,需要设置编码。
假如我要添加的附件是下面这个:
excel_file_path = r'E:\WorkSpace\pythonProjects\Ontime_Script\attach_table\recurring_pay_failed_user_info_2018-05-09.xlsx'
我写的是windows下的全路径,分隔符使用的是单个的正斜杠,前面加了个r,可以使那种类似转义符的代码失效。
如果是Linux下,该是这样的:
excel_file_path = '/root/pythonProjects/Ontime_Script/attach_table/recurring_pay_failed_user_info_2018-05-09.xlsx'
写成全路径,无论是在哪个目录下运行该脚本,都不会出现错误,这点要注意,相对路径是很坑人的。
# 构造附件,测试成功,附件有很多类型,现在构建的是html文件
attach_table = MIMEApplication(open(excel_file_path, 'rb').read())
# 给附件增加标题
attach_table.add_header('Content-Disposition', 'attachment',filename='我的附件.xlsx')
# 这样的话,附件名称就可以是中文的了,不会出现乱码
attach_table.set_charset('utf-8')
msg.attach(attach_table)
上面就是为msg对象添加附件的全过程了:先将xlsx文件读成二进制,作为参数构造MIMEApplication类,第二步是为这个附件添加名称(这个名称是在邮件中显示的),如果是中文,还需要设置一下编码,否则中文会显示为乱码,最后添加到msg中。
下面就是此节的全部代码:
# 导入相关库-email
from email.mime.multipart import MIMEMultipart # 构建邮件头信息,包括发件人,接收人,标题等
from email.mime.text import MIMEText # 构建邮件正文,可以是text,也可以是HTML
from email.mime.application import MIMEApplication # 构建邮件附件,理论上,只要是文件即可,一般是图片,Excel表格,word文件等
from email.header import Header # 专门构建邮件标题的,这样做,可以支持标题中文
import smtplib
def send_email(html_msg):
"""发送邮件的脚本"""
# 邮件服务信息,个人
# smtp_server = 'smtp.163.com'
# username = "lihua.0221@163.com"
# password = 'xxxxx' # 授权码,并不是邮箱登陆密码
# 邮件服务信息,公司
smtp_server = 'smtp.ym.163.com'
username = "lihua@everimaging.com"
password = 'xxxxxx' # 授权码,企业163的就是登陆密码
# 邮件发送和接收人
sender = username
receiver = ['lihua@everimaging.com', '724694053@qq.com']
# 邮件头信息
msg = MIMEMultipart('related')
msg['Subject'] = Header("我的第一封python邮件")
msg["From"] = sender
msg['To'] = ','.join(receiver) # 这里要注意
html_msg = \
"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是一封python写的邮件,使用的是HTML格式构造正文</h1>
<h2>可以为文字添加超链接,比如:<a href="//www.greatytc.com/u/8159970c6959">简书-小溏</a></h2>
<hr>
<h3>还可以添加图片,比如下面这张</h3>
<img src="https://pub-static.haozhaopian.net/assets/projects/export/jpg/29736970-a991-4b91-91af-854a8eb561e6.jpg">
</body>
</html>
"""
# html 内容
content_html = MIMEText(html_msg, "html", "utf-8")
msg.attach(content_html)
excel_file_path = r'E:\WorkSpace\pythonProjects\Ontime_Script\email_market_report\attach_table\recurring_pay_failed_user_info_2018-05-09.xlsx'
# 构造附件,测试成功,附件有很多类型,现在构建的是xlsx文件
attach_table = MIMEApplication(open(excel_file_path, 'rb').read())
# 给附件增加标题
attach_table.add_header('Content-Disposition', 'attachment',filename='我的附件.xlsx')
# 这样的话,附件名称就可以是中文的了,不会出现乱码
attach_table.set_charset('utf-8')
msg.attach(attach_table)
# 发送邮件,测试成功,流程都是固定的:创建客户端,登陆,发送,关闭
email_client = smtplib.SMTP(smtp_server)
email_client.login(username, password)
email_client.sendmail(sender, receiver, msg.as_string())
email_client.quit()
if __name__ == '__main__':
send_email()
如果你需要添加其他类型的附件,比如图片,word,压缩包等,都是一样的,只是把文件名换一下即可,还有附件名称的后缀。
学到此处,基本的邮件发送是没问题了,但,往往实际工作中没有这么简单,HTML代码不是现成的,附件也需要临时生成等等。
3. 将Dataframe转为HTML,用CSS美化表格,设置URL,使之显示完全等细节工作
定时邮件,经常会伴随着实时数据的展现,在python中使用最多的是pandas包下面的DataFrame类,它就像一个Excel表格一样,可以完美的展示数据,但是,我们该如何将它完美的展现在邮件的正文中呢?
现在我给出一个示例的表格数据:
源数据链接:https://pan.baidu.com/s/1m9v3i153-M17Q8D6cmnMjw 密码:k8pk
如果是直接将这个Excel文件作为附件发送到邮件里,查看邮件的人是很苦恼的,首先还要下载附件,打开Excel,后面的缩略图还只是URL,想看具体长什么样,还得一个一个点开在浏览器里面查看。
现在,如果用HTML正文格式展示这个表格,上面讲到的痛点都会解决。
首先,我们读取Excel文件成为Dataframe格式
import pandas as pd
df = pd.read_excel(r"E:\WorkSpace\pythonProjects\Ontime_Script\attach_table\template_use.xlsx")
Dataframe有一个函数是to_html(),可以直接将df转为HTML中的table,它的参数相当多
def to_html(self, buf=None, columns=None, col_space=None, header=True,
index=True, na_rep='NaN', formatters=None, float_format=None,
sparsify=None, index_names=True, justify=None, bold_rows=True,
classes=None, escape=True, max_rows=None, max_cols=None,
show_dimensions=False, notebook=False, decimal='.',
border=None):
现在,我们先把df中的缩略图包装一下,以适应HTML中显示图片的格式
df['缩略图'] = '<img src="' + df['缩略图'] + '">'
然后执行转换
df_html = df.to_html(escape=False)
escape这个参数是:Convert the characters <, >, and & to HTML-safe sequences.=
就是说,我们df里面,如果有HTML的特有元素,是转化为转义的呢?还是保持本身的样子不变。显然,我们设置了img的格式,要在HTML中展示图片,比如下面这个
<img src="https://pub-static.haozhaopian.net/assets/projects/export/jpg/dd73de46-7b9b-45ca-b89b-651843304f59.jpg">
我们不希望将<这种符号变为<,而是保持本身的样子,所以将escape设置为False,(也许我没有讲清楚,不过你可以在实践中设置为True,看看最终会发生什么,其实,默认的就是True)。
现在,我们的任务就是,构造一个HTML的完整格式,然后将这个df_html 放进去即可,对于HTML的美化,我也一并放在代码中了,不在这里一一讲解。
本节全部的代码:
import pandas as pd
# 导入相关库-email
from email.mime.multipart import MIMEMultipart # 构建邮件头信息,包括发件人,接收人,标题等
from email.mime.text import MIMEText # 构建邮件正文,可以是text,也可以是HTML
from email.mime.application import MIMEApplication # 构建邮件附件,理论上,只要是文件即可,一般是图片,Excel表格,word文件等
from email.header import Header # 专门构建邮件标题的,这样做,可以支持标题中文
pd.set_option('display.max_colwidth', -1) # 能显示的最大宽度, 否则to_html出来的地址就不全
def get_html_msg():
"""
1. 构造html信息
"""
df = pd.read_excel(r"E:\WorkSpace\pythonProjects\Ontime_Script\email_market_report\attach_table\template_use.xlsx")
df['缩略图'] = '<img src="' + df['缩略图'] + '">'
df_html = df.to_html(escape=False)
head = \
"""
<head>
<meta charset="utf-8">
<STYLE TYPE="text/css" MEDIA=screen>
table.dataframe {
border-collapse: collapse;
border: 2px solid #a19da2;
/*居中显示整个表格*/
margin: auto;
}
table.dataframe thead {
border: 2px solid #91c6e1;
background: #f1f1f1;
padding: 10px 10px 10px 10px;
color: #333333;
}
table.dataframe tbody {
border: 2px solid #91c6e1;
padding: 10px 10px 10px 10px;
}
table.dataframe tr {
}
table.dataframe th {
vertical-align: top;
font-size: 14px;
padding: 10px 10px 10px 10px;
color: #105de3;
font-family: arial;
text-align: center;
}
table.dataframe td {
text-align: center;
padding: 10px 10px 10px 10px;
}
body {
font-family: 宋体;
}
h1 {
color: #5db446
}
div.header h2 {
color: #0002e3;
font-family: 黑体;
}
div.content h2 {
text-align: center;
font-size: 28px;
text-shadow: 2px 2px 1px #de4040;
color: #fff;
font-weight: bold;
background-color: #008eb7;
line-height: 1.5;
margin: 20px 0;
box-shadow: 10px 10px 5px #888888;
border-radius: 5px;
}
h3 {
font-size: 22px;
background-color: rgba(0, 2, 227, 0.71);
text-shadow: 2px 2px 1px #de4040;
color: rgba(239, 241, 234, 0.99);
line-height: 1.5;
}
h4 {
color: #e10092;
font-family: 楷体;
font-size: 20px;
text-align: center;
}
td img {
/*width: 60px;*/
max-width: 300px;
max-height: 300px;
}
</STYLE>
</head>
"""
# 构造模板的附件(100)
body = \
"""
<body>
<div align="center" class="header">
<!--标题部分的信息-->
<h1 align="center">我的python邮件,使用了Dataframe转为table </h1>
</div>
<hr>
<div class="content">
<!--正文内容-->
<h2>带图片展示的表格</h2>
<div>
<h4></h4>
{df_html}
</div>
<hr>
<p style="text-align: center">
—— 本次报告完 ——
</p>
</div>
</body>
""".format(df_html=df_html)
html_msg= "<html>" + head + body + "</html>"
# 这里是将HTML文件输出,作为测试的时候,查看格式用的,正式脚本中可以注释掉
fout = open('t4.html', 'w', encoding='UTF-8', newline='')
fout.write(html_msg)
return html_msg
def send_data_df(html_msg):
"""发送邮件的脚本"""
# 邮件服务信息,个人
# smtp_server = 'smtp.163.com'
# username = "lihua.0221@163.com"
# password = 'xxxxxx' # 授权码,并不是邮箱登陆密码
# 邮件服务信息,公司
smtp_server = 'smtp.ym.163.com'
username = "lihua@everimaging.com"
password = 'xxxxxx' # 授权码,企业163的就是登陆密码
# 邮件发送和接收人
sender = username
receiver = ['lihua@everimaging.com', '724694053@qq.com']
# 邮件头信息
msg = MIMEMultipart('related')
msg['Subject'] = Header("我的第一封python邮件")
msg["From"] = sender
msg['To'] = ','.join(receiver) # 这里要注意
# html 内容
content_html = MIMEText(html_msg, "html", "utf-8")
msg.attach(content_html)
# 发送邮件,测试成功,流程都是固定的:创建客户端,登陆,发送,关闭
email_client = smtplib.SMTP(smtp_server)
email_client.login(username, password)
email_client.sendmail(sender, receiver, msg.as_string())
email_client.quit()
if __name__ == '__main__':
html_msg = get_html_msg()
send_data(html_msg)
上面的代码里,CSS占了很长一段,我没有解释,如果有任何问题,请联系我。
另外,这段代码里还有一个很重要的
pd.set_option('display.max_colwidth', -1) # 能显示的最大宽度, 否则to_html出来的地址就不全
这是设置Dataframe的显示宽度的,因为缩略图那一列的内容很长,如果没有上诉设置的话,转出来的地址就被省略了一部分,使得无法显示图片,如果大家想测试其功能,可以注释掉,看看会发生什么结果。最后,附上这个邮件的发送结果:
很漂亮有木有~~~~,一眼就看到了图片长什么样,还有跟它相关的信息在一起,也不用下载附件再点开url了。
还有,如果大家想知道没有CSS美化的结果是什么样,可以注释掉CSS部分代码,然后运行看一看。
今天的分享就这样了,祝大家学习愉快。