小白入门,用python 发送定时邮件,将Dataframe转为邮件正文,链接显示为图片

1字数 2995阅读 3369

在实际工作中,我们常常会遇到定时发送邮件的任务,基于我的实践,分享给大家,也许一篇文章写不完,就先列个目录。

本文想要解决的问题:

  1. 用python构造一封邮件,并设置定时发送出去。往往,这只是最低级的需求,实际工作中会有各种细节和附加条件。
  2. 学会构造文本、HTML两种格式的邮件正文,学会构造和添加附件,其中,HTML格式要学会使用超链接,学会添加CSS用来美化正文。
  3. 将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个人邮箱的发件服务器

    上面箭头所指,就是163个人邮箱的发件服务器:smtp.163.com,如果是163的企业邮箱,其服务器地址则是:smtp.ym.163.com,我就是用的这个。
    下面我要将的内容很关键:163个人邮箱的密码,不是登陆密码,而是客户端授权码,如下图所示:
    image.png

    我们这里是属于使用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="https://www.jianshu.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>

在浏览器中显示如下:


image.png

现在,我将它放进我的邮件正文中,使用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="https://www.jianshu.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个问题需要重视:
    1. 附件的路径,最好是使用全路径。
    1. 附件的中文名称,需要设置编码。
      添加附件前,我们需要确定附件的位置,也就是在系统上的路径。关于路径,我是吃过大亏的,所以各位新手也要注意,在不同的系统(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="https://www.jianshu.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

image.png

如果是直接将这个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的显示宽度的,因为缩略图那一列的内容很长,如果没有上诉设置的话,转出来的地址就被省略了一部分,使得无法显示图片,如果大家想测试其功能,可以注释掉,看看会发生什么结果。最后,附上这个邮件的发送结果:


image.png

很漂亮有木有~~~~,一眼就看到了图片长什么样,还有跟它相关的信息在一起,也不用下载附件再点开url了。
还有,如果大家想知道没有CSS美化的结果是什么样,可以注释掉CSS部分代码,然后运行看一看。
今天的分享就这样了,祝大家学习愉快。

推荐阅读更多精彩内容