一.引言
上一篇文章 Python - openpyxl Excel 操作示例与实践 介绍了如何将数据自动转化至 Excel 并完成自定义标注,节省了大量人工操作的时间,但是后续如果需要将生成的 Excel 和数据发送邮件到指定同学就还需要一步人工操作时间即写邮件发邮件,非常的不奈斯,下面结合 smtplib 库实现自定义邮件的发送,从而实现 数据 -> Excel -> 邮件发送的全自动需求。理想的效果是数据以表格的形式在邮件中展示,并且完整的 Excel 在附件中:
编辑
二.smtplib 库相关介绍
1.smtplib 库简介
smtplib - Simple Mail Transfer Protocol 即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。python 的 smtplib 提供了一种很方便的途径发送电子邮件。它对smtp协议进行了简单的封装,发送邮件只需要几个简单参数即可实现邮件的自动发送:
import smtplib # 初始化服务 smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )
host | 服务器主机,一般由公司IP部门提供 |
port | host 对应的端口号,一般情况为25 |
local_hostname | SMTP 在你的本机上可以这样指定 |
# 发送邮件 SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options])
from_addr | 邮件发送者地址 |
to_addrs | 字符串列表,邮件发送地址 |
msg | 要发送的信息,对应 email 类中的多种类型 |
2.smtplib 库常用数据类型
Type:
Text | 发送文本信息 |
Multipart | 用于发送连接多种类型的信息 |
Application | 用于传输二进制数据 |
Message | 用户包装 Email 信息 |
Image | 用户传输图片 |
Audio | 用于传输音频类数据 |
Video | 用于传输视频类数据 |
SubType:
text - plain | 纯文本 |
text - html | HTML 文档 |
multipart - alternative | HTML邮件的HTML形式和纯文本形式,相同内容使用不同形式表示 |
multipart - form-data | 主要用于提交时包含附件 |
application/xhtml+xml | XHTML文档 |
application/octet-stream | 任意的二进制数据 |
application/pdf | PDF文档 |
application/msword | Microsoft Word文件 |
application/vnd.wap.xhtml+xml | wap1.0+ |
application/x-www-form-urlencoded | 使用HTTP的POST方法提交的表单 |
message/rfc822 | RFC 822形式 |
image/png | PNG 图像 |
image/gif | GIF 图像 |
image/jpeg | JPEG 图像 |
video/mpeg | MPEG 动画 |
可以通过上一篇文章提到的 openpyxl 库进行文件类型的推断,给定一个 png 图像,guess_type 方法会自动推断对应文件的 MainType 以及 Subtype:
ctype, encoding = mimetypes.guess_type('bash.png') if ctype is None or encoding is not None: ctype = 'application/octet-stream' maintype, subtype = ctype.split('/', 1) print(maintype, subtype)
image png
3.初始化本地服务器 (不推荐)
local 模式下直接指定端口号即可,但是需要在本地启动服务器
smtpObj = smtplib.SMTP('localhost', 1025) smtpObj.sendmail(sender, receivers, message.as_string())
打开终端 terminal 执行:
python -m smtpd -n -c DebuggingServer localhost:999
发送后会有如下提示:
编辑
但是有个问题是我们本地一般不会配置邮箱服务器或自己的账号系统,所以推荐使用下面的非 localhost 模式。
4.初始化官方服务器 (推荐👍)
常用的邮箱有 qq,163,分别对应的官方服务器网址为: smtp.qq.com 和 smtp.163.com,下面初始化 qq邮箱 发送的客户端:
smtpObj = smtplib.SMTP('smtp.qq.com', 25) smtpObj.ehlo() smtpObj.starttls() # token - 需要通过qq网页获取 smtpObj.login("xxxxxxx@qq.com", "校验码") smtpObj.sendmail(sender, receivers, message.as_string())
使用 qq 邮箱时,client 需要 login 并验证你作为 sender 的资格,第一个参数为你的 qq邮箱地址,第二个校验码需要通过邮箱配置获取,下面看一下获取步骤:
A.进入网页版 qq 邮箱
编辑
B.选择账户选项
编辑
C.配置 Smtp
下拉账户菜单栏到如下位置,可以看到除了 SMTP 还有很多邮箱服务,开启 SMTP 服务
编辑
D.发送验证码
开启上述 SMTP 服务后会要求你发送验证信息到官方,成功发送后点击 '我已发送' 即可拿到官方发送的校验码,复制粘贴保存下来。
编辑
网易 163邮箱的配置方法和上面大同小异这里不多赘述。
三.smtplib 库常用操作
通过上面第二步操作我们已经初始化好 SMTP 的邮件服务器,邮件的常规操作大致如下:
A.写字 ✍️
B.发链接 🔗
C.发图 ⛰
D.发附件 📃
E.发表格 📚
首先初始化 QQ 邮箱服务器,然后一一实践:
smtpObj = smtplib.SMTP('smtp.qq.com', 25) smtpObj.ehlo() smtpObj.starttls() # token - 需要通过qq网页获取 smtpObj.login("sender@qq.com", "token")
1.写字 ✍️
写字主要使用 Text type 下的 plain 模式即可:
subject = 'Python SMTP 测试邮件标题' message = MIMEText('Python 邮件发送测试...', 'plain', 'utf-8') message['From'] = Header("BITDDD", 'utf-8') # 发送者 message['To'] = Header("测试账户", 'utf-8') # 接收者 message['Subject'] = Header(subject, 'utf-8') sender = 'sender@qq.com' receivers = ['receiver@qq.com'] smtpObj.sendmail(sender, receivers, message.as_string())
执行后在 reveiver 对应的 qq 邮箱处收到如下信息:
编辑
2.发链接 🔗
发链接的操作其实很常见,很多验证信息,广告都是通过网页链接实现:
mail_msg = """ <p>Python 邮件发送链接测试...</p> <p><a href="https://blog.csdn.net/BIT_666?type=blog">BITDDD</a></p> """ subject = 'Python SMTP 测试发送链接标题' message = MIMEText(mail_msg, 'html', 'utf-8') message['From'] = Header("BITDDD", 'utf-8') message['To'] = Header("测试账户", 'utf-8') message['Subject'] = Header(subject, 'utf-8') smtpObj.sendmail(sender, receivers, message.as_string())
发送后如下,可以修改 mail_msg 里 >BITDDD< 的内容作为链接的中文描述,例如恭喜你中奖了之类的,这样有同学就会误点这个链接了~
编辑
3.发图 ⛰
编辑编辑
msgAlternative = MIMEMultipart('alternative') msgRoot.attach(msgAlternative) mail_msg = """ <p>Python 邮件发送图片测试...</p> <p>图片演示:</p> <p><img src="cid:image1"></p> <p>图片演示:</p> <p><img src="cid:image2"></p> """ msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 指定图片为当前目录 fp = open('A.jpg', 'rb') msgImage = MIMEImage(fp.read()) fp.close() # 定义图片 ID,在 HTML 文本中引用 msgImage.add_header('Content-ID', '<image1>') msgRoot.attach(msgImage) # 指定图片为当前目录 fp = open('B.png', 'rb') msgImage2 = MIMEImage(fp.read()) fp.close() # 定义图片 ID,在 HTML 文本中引用 msgImage2.add_header('Content-ID', '<image2>') msgRoot.attach(msgImage2) smtpObj.sendmail(sender, receivers, msgRoot.as_string())
这里采用 MIMEMultipart 添加两幅图片, 如果需要继续添加,修改 mail_msg 里的信息,并在后面 attach 相关图片的二进制信息即可:
编辑
4.发附件 📃
发邮件很多时候需要附带 word、excel、txt 等文件信息,下述示例将添加两个附件并发送,如果有更多附件也可以模仿累加即可, Content-Disposition 里的 filename 对应该文件在邮件中展示的名字:
message = MIMEMultipart() message['From'] = Header("BITDDD", 'utf-8') message['To'] = Header("测试账户", 'utf-8') subject = 'Python SMTP 邮件附件测试' message['Subject'] = Header(subject, 'utf-8') #构造附件1,传送当前目录下的 test.txt 文件 att1 = MIMEText(open('test1.txt', 'rb').read(), 'base64', 'utf-8') att1["Content-Type"] = 'application/octet-stream' att1["Content-Disposition"] = 'attachment; filename="test1.txt"' message.attach(att1) # 构造附件2,传送当前目录下的 runoob.txt 文件 att2 = MIMEText(open('test2.txt', 'rb').read(), 'base64', 'utf-8') att2["Content-Type"] = 'application/octet-stream' att2["Content-Disposition"] = 'attachment; filename="test2.txt"' message.attach(att2) smtpObj.sendmail(sender, receivers, message.as_string()) print("邮件发送成功")
执行后获得如下邮件:
编辑
5.发表格 📚
表格为上一篇文章得到的自定义数据报表:
编辑
msgRoot = MIMEMultipart('mixed') msgRoot['From'] = Header("BITDDD", 'utf-8') # 发送者 msgRoot['To'] = Header("测试账户", 'utf-8') # 接收者 subject = 'Python SMTP 邮件表格测试' msgRoot['Subject'] = Header(subject, 'utf-8') df = pd.read_excel("test123.xlsx") # 添加表格 html_msg = get_html_msg(df.to_html(escape=False)) content_html = MIMEText(html_msg, "html", "utf-8") msgRoot.attach(content_html) smtpObj.sendmail(sender, receivers, msgRoot.as_string())
通过 DataFrame 的 to_html 方法获取其对应的 HTML 格式,并添加到 Text - html 类下,执行后收到如下邮件:
编辑
Tips:
get_html_msg 函数如下,内置了生成 html-table 的标准语法,如果需要添加多个表格,可以增加多个 head 和 body 标注表格类型,从而展示多张数据表:
def get_html_msg(df_html): 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> """ body = \ """ <body> <div align="center" class="header"> <!--标题部分的信息--> <!-- <h1 align="center">我的python邮件,使用了Dataframe转为table </h1> --> </div> <hr> <div class="content"> <!--正文内容--> <h2>第一个Dataframe</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>" fout = open('table.html', 'w', encoding='UTF-8', newline='') fout.write(html_msg) return html_msg
四.smtplib 实践
上一篇文章通过 openpyxl 库实现了原始数据到 Excel 的转化:
编辑
下面结合 smtplib 库实现 Excel 到邮件的转化:
编辑
这里先分析下需要做哪些事情:
A.添加邮件图片 ①
B.添加 DataFrame ②
C.添加对应 DataFrame 的 Excel 附件 ③
完整代码:
#!/usr/bin/python # -*- coding: UTF-8 -*- import smtplib from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.header import Header import pandas as pd import numpy as np from openpyxl.packaging.manifest import mimetypes # 发送者邮箱 sender = 'sender@qq.com' receivers = ['receiver@qq.com'] # ===============================设置邮件标题============================== fromTitle = "BITDDD" receiverTitle = "测试账户" msgRoot = MIMEMultipart('mixed') msgRoot['From'] = Header(fromTitle, 'utf-8') # 发送者 msgRoot['To'] = Header(receiverTitle, 'utf-8') # 接收者 # 邮件主题 subject = 'Python SMTP 邮件测试 By BITDDD' msgRoot['Subject'] = Header(subject, 'utf-8') # ===============================连接服务器============================== smtpObj = smtplib.SMTP('smtp.qq.com', 25) smtpObj.ehlo() smtpObj.starttls() smtpObj.login("sender@qq.com", "token") # ===============================添加图片============================== fp = open('excel.jpg', 'rb') msgAlternative = MIMEMultipart('alternative') mail_msg = """ <p>Python 邮件发送测试...</p> <p>图片演示:</p> <p><img src="cid:image1"></p> """ msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 指定图片为当前目录 msgImage = MIMEImage(fp.read(), subtype) msgImage.add_header('Content-ID', '<image1>') fp.close() msgAlternative.attach(msgImage) msgRoot.attach(msgAlternative) # ===============================添加df============================== df = pd.read_excel("test123.xlsx") # 添加表格 html_msg = get_html_msg(df.to_html(escape=False)) content_html = MIMEText(html_msg, "html", "utf-8") msgRoot.attach(content_html) # =============================添加附件=============================== attachCsv = MIMEText(open(savePath, 'rb').read(), 'base64', 'utf-8') attachCsv["Content-Type"] = 'application/octet-stream' attachCsv["Content-Disposition"] = 'attachment; filename="test.xlsx"' msgRoot.attach(attachCsv) smtpObj.sendmail(sender, receivers, msgRoot.as_string())
这里补充一下添加混合类型 MIMEMultipart 时的几种模式:
mixed | 混合类型 |
related | 内嵌资源如附件 |
alternative | 文本与超文本 |
上述示例同时使用了 图片、附件、超文本 ,初始化可以采用 mixed 、alternative,如果使用 related ,则会解析异常,DataFrame 的数据会变成二进制 bin 文件发送到 receiver 邮箱中:
编辑
五.smtplib 抄送
除了发送邮件外,有时还需要抄送其他同学,smtplib 同样支持该操作。
A.单独发送
sender = 'xxxxA@qq.com' receivers = ['xxxxB@qq.com'] msgRoot = MIMEMultipart('alternative') msgRoot['From'] = Header(fromTitle, 'utf-8') # 发送者 msgRoot['To'] = ','.join(receiverTitle) # 接收者 # 邮件主题 subject = 'Python SMTP 邮件测试 By BITDDD' msgRoot['Subject'] = Header(subject, 'utf-8') smtpObj.sendmail(sender, receivers, msgRoot.as_string())
B.设置抄送
sender = 'xxxxA@qq.com' receivers = ['xxxxB@qq.com'] cc = ['xxxxC.com', "xxxxD.com"] msgRoot = MIMEMultipart('alternative') msgRoot['From'] = Header(fromTitle, 'utf-8') # 发送者 msgRoot['To'] = ','.join(receiverTitle) # 接收者 msgRoot['Cc'] = ','.join(cc) # 抄送者 # 邮件主题 subject = 'Python SMTP 邮件测试 By BITDDD' msgRoot['Subject'] = Header(subject, 'utf-8') smtpObj.sendmail(sender, receivers + cc, msgRoot.as_string())
可以看到增加抄送总共分三步:
-> 添加抄送列表 cc
-> 将 cc 添加至 msg 中
-> sendmail 写成 reveiver + cc
编辑
六.总结
通过 openpyxl + smtplib 实现了数据到表格到邮件的转化,网页版邮箱接收会归类到 广告 或者 垃圾邮件中,需要手动找一下,不过手机上接收没有问题,非常的奈斯~
编辑