一、了解SMTP协议
-
目前用于发送邮件的协议是SMTP(简单文本传输协议)
-
python内置的smtplib模块提供了便捷的邮件发送方法,可以发送纯文本邮件、HTML邮件及带附件的邮件。
-
Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件
-
python创建SMTP对象的语法如下
import smtplib
smtpobj = smtplib.SMTP([host [, port [, local_hostname] ] ])参数说明:
- host :SMTP 服务器主机,可以指定主机的IP地址或域名,是可选参数
- port :如果提供了host参数,就需要指定SMTP服务使用的端口号,一般情况下SMTP的端口号是 25
- local_hostname :如果SMTP在你的本机上,那么只需要指定服务器地址为localhost即可。
-
pythonSMTP随想使用sendmail方法发送邮件,其语法如下:
SMTP.sendmail(from_addr, to_addr, msg[, mail_options, rcpt_options] )
参数说明:
- from_addr :邮件发送者地址
- to_addr :字符串列表,邮件发送地址
- msg :发送消息(这个参数msg是字符串,表示邮件本身。发邮件时一定要注意格式,一般是由标题、发信人、收件人、邮件内容、附件等组成,因此发邮件时要注意msg的格式。这个格式需要是SMTP定义的格式)
- 简单文本邮件模板
from email.mime.text import MIMEText
message = MIMEText(‘Python 邮件测试…’, ‘plain’, ‘utf-8’)
----------------------------------------------------------------------------------------------------------
# 构造MIMEText对象时 ,第一个参数就是邮件正文,第二个参数是MIMEText的subtype,传入palin,最终MIMEText就是’text/plain’,最后一定要用’utf-8’编码,保证语言的兼容性
二、编写发送邮件代码
网易邮箱服务器发送邮件常见问题:http://help.163.com/09/1224/17/5RAJ4LMH00753VB8.html
1.文本格式邮件
邮件代码:
import smtplib
from email.mime.text import MIMEText
# 第三方SMTP服务
mail_host = "smtp.qq.com" # 设置服务器(smtp.163.com是网易邮箱服务器,smtp.qq.com是QQ地址)
mail_user = "xxxxxxxx@qq.com" # 用户名
mail_pass = "aydzptaydrdpched" # 密码(注意,这里的密码指的是邮箱客户端授权码)
# 发送者邮箱
sender = "xxxxx@qq.com"
# 接收者邮箱
receivers = ['xxxx@163.com', 'xxxxx@qq.com'] # 邮件接受者,可设置多个
# 构造正文
message = MIMEText('这是正文:python test。。。', 'plain', 'utf-8') # 构造正文
# 构造发件人
message["From"] = sender # 发件人,这个必须构造,也可以使用Header来构造
# 收件人列表,这个不是必须的
message["To"] = ";".join(receivers)
# 定义邮件主题
message["Subject"] = "这是主题:SMTP 邮件测试"
try:
smtpObj = smtplib.SMTP() # 建立和SMTP邮件服务器的连接
smtpObj.connect(mail_host, 25) # 25 为端口号
smtpObj.login(mail_user, mail_pass)
smtpObj.sendmail(sender, receivers, message.as_string())
print('send mail Successfully')
smtpObj.quit() # 结束会话
except smtplib.SMTPException as e:
print(f"发送失败,错误原因:{e}")
执行结果:
[root@localhost MSTP]# ./sendmail.py
send mail Successfully
2.html正文格式邮件
- 只需要将正文message部分进行修改即可
例如:
message = MIMEText(
'<html><body><h1>这是正文标题</h1>\
<p>正文内容<a href="#">https://blog.csdn.net/CN_LiTianpeng?spm=1001.2014.3001.5343 #《超链接》</a>...</p>\
</html></body>',
"html",
"utf-8"
)
3.添加附件发送邮件
- 同样需要将正文message部分进行修改即可
[root@localhost MSTP]# vim sendmail.py
#!/bin/env python3
import smtplib
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.header import Header
# 第三方SMTP服务
mail_host = "smtp.qq.com" # 设置服务器(smtp.163.com是网易邮箱服务器,smtp.qq.com是QQ地址)
mail_user = "xxxxxxxx@qq.com" # 用户名
mail_pass = "aydzptaydrdpched" # 密码(注意,这里的密码指的是邮箱客户端授权码)
# 发送者邮箱
sender = "xxxxx@qq.com"
# 接收者邮箱
receivers = ['xxxx@163.com', 'xxxxx@qq.com'] # 邮件接受者,可设置多个
# 构造正文
# # -------------------- 文本类邮件 -------------------------------------------------
# message = MIMEText('这是正文:python test。。。', 'plain', 'utf-8') # 构造正文
# 构造正文
# # ------------------- html类邮件 -------------------------------------------------
# message = MIMEText(
# '<html><body><h1>这是正文标题</h1>\
# <p>正文内容<a href="#">https://blog.csdn.net/CN_LiTianpeng?spm=1001.2014.3001.5343 #《超链接》</a>...</p>\
# </html></body>',
# "html",
# "utf-8"
# )
# 构造正文
# ---------------------可添加附件的邮箱--------------------------------------------------
message = MIMEMultipart()
# 邮件正文部分
message.attach(MIMEText('<p>这是正文:图片及附件发送测试</p><p>图片演示</p><p><img src="cid:image1"></p>', 'html', 'utf-8'))
# 指定图片路径
fp = open("baidu2.jpg", 'rb')
msgImage = MIMEImage(fp.read())
fp.close()
# 定义图片ID,在Html中引用
msgImage.add_header("Content_ID", "<image1>")
message.attach(msgImage)
# 添加附件1,传送当前目录下的test.txt文件
att1 = MIMEText(open("test.txt", "rb").read(), "base64", "utf-8")
att1["Content-Type"] = "application/octet-stream"
# 这里的filename可以任意写,写什么名称,在邮件中就会显示什么名称
att1["Content-Dispostion"] = "attachment; filename='测试文件' "
message.attach(att1)
# --------------------------------------------------------------------
# 构造发件人
message["From"] = sender # 发件人,这个必须构造,也可以使用Header来构造
# 收件人列表,这个不是必须的
message["To"] = ";".join(receivers)
# 定义邮件主题
message["Subject"] = "这是主题:SMTP 邮件测试"
try:
smtpObj = smtplib.SMTP() # 建立和SMTP邮件服务器的连接
smtpObj.connect(mail_host, 25) # 25 为端口号
smtpObj.login(mail_user, mail_pass)
smtpObj.sendmail(sender, receivers, message.as_string())
print('send mail Successfully')
smtpObj.quit() # 结束会话
except smtplib.SMTPException as e:
print(f"发送失败,错误原因:{e}")
三、利用poplib模块来收取邮件
1.收取步骤
- 第一步:用poplib模块将邮件的原始文本下载到本地
- 第二步:用email模块解析原始文本,还原邮件对象
2.应用示例
四、实时邮箱告警
监控示例
监控要求:
- 文本文件txt约定格式:第一行为收件人列表,以分号隔开;第二行为主题,第三行至最后为正文。如最后一行是文件,则作为附件发送,支持发送多个附件,以逗号隔开
xxx@163.com;xxx@qq.com
xxx 程序告警
告警信息:…
…
…
/home/log/xxx.log,/tmp/yyy.log - 持续监控一个目录下的txt文件,如果有新增或者修改,则读取文本中的内容并发送邮件
- 涉及的知识点:文件编码。读文件操作、watchdog模块应用及邮件发送
告警代码:
import smtplib
import chardet
import codecs
import os
from email.mime.text import MIMEText
from email.header import Header
from email.mime.multipart import MIMEMultipart
# 接收告警的邮箱列表
recipient_list = ['xxx@163.com', 'xxx@qq.com']
# 第三方SMTP服务
class TxtMali(object):
def __init__(self, host=None, auth_user=None, auth_pass=None):
# 设置发送告警的邮件服务器
self.host = "smtp.qq.com" if host is None else host
# 设置专用告警账户的用户名
self.auhe_user = "xxx@qq.com" if auth_user is None else auth_user
# 设置专用告警账户的用户的密码
self.auth_pass = "xxxxxxxxxxxx" if auth_pass is None else auth_pass
self.sender = "xxxxxx@qq.com"
def send_mail(self, subject, msg_str, recipient_list, attachment_list=None):
messgae = MIMEMultipart()
messgae["From"] = self.sender
messgae["To"] = Header(';'.join(recipient_list), "utf-8")
messgae["Subject"] = Header(subject, 'utf-8')
messgae.attach(MIMEText(msg_str, 'plain', 'utf-8'))
# 如果有附件,则添加附件
if attachment_list:
for att in attachment_list:
attachment = MIMEText(open(att, "rb").read(), "base64", "utf-8")
attachment["Content-Type"] = "application/octet-stream"
# 这里的filename可以任意写,会在邮件中显示
# attname = att.split("/")[-1]
filename = os.path.basename(att)
# attm["Content-Disposition"] = 'attachment;filename=%s'%attname
attachment.add_header(
"Content_Disposition",
"attachment",
filename=('utf-8', "", filename)
)
smptObj =smtplib.SMTP_SSL()
smptObj.connect(self.host, smtplib.SMTP_SSL_PORT)
smptObj.login(self.auhe_user, self.auth_pass)
smptObj.sendmail(self.sender, recipient_list, messgae.as_string())
smptObj.quit()
print("邮件发送成功")
def guess_chardet(self, filename):
"""
:param filename: 传入一个文本文件
:return: 返回文本文件的编码格式
"""
encoding = None
try:
# 由于本需求解析的文件不大,可以一次性读入内存
# 如果是大文件,则读取固定字节数
raw = open(filename, 'rb').read()
if raw.startswith(codecs.BOM_UTF8): # 处理UTF-8 with BOM
encoding = "utf-8-fig"
else:
result = chardet.detect(raw)
encoding = result["encoding"]
except:
pass
return encoding
def txt_sen_dmail(self, filename):
"""
:param filename:
:return:
将指定格式的txt文件发送到邮箱,txt文件样式如下:
xxx@163.com;xxx@qq.com # 收件人
xxx 程序告警 # 主题
告警信息:...... # 正文
......
file1,file2 # 附件
"""
with open(filename, encoding=self.guess_chardet(filename)) as f:
lines = f.readlines()
recipient_list = lines[0].strip().split(",")
subject = lines[1].strip()
msg_str = "".join(lines[2:])
attachment_list = []
for file in lines[-1].strip().split(","):
if os.path.isfile(file):
attachment_list.append(file)
# 如果没有这个附件,则为None
if attachment_list == []:
attachment_list = None
self.send_mail(
subject=subject,
msg_str=msg_str,
recipient_list=recipient_list,
attachment_list=attachment_list
)
if __name__ == '__main__':
mymail = TxtMali()
mymail.txt_sen_dmail(filename="./test.txt")
监控文件脚本:
# ----------------------------
#文件监控
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from test_alert_mail import TxtMail
class FileEventHandler(FileSystemEventHandler):
def __init__(self):
FileSystemEventHandler.__init__(self)
def on_created(self, event):
if event.is_directory:
print("directory is created:{0}".format(event.src_path))
else:
print("file created:{0}".format(event.src_path))
if event.src_path.endswith(".txt"):
time.sleep(0.5)
mail = TxtMail()
try:
mail.txt_send_mail(filename=event.src_path)
except:
print("文本文件格式不正确")
if __name__ == '__main__':
observer = Observer()
event_handler = FileEventHandler()
dir = "./"
observer.schedule(event_handler, dir, False)
print(f"当前监控的目录:{dir}")
observer.start()
observer.join()
邮箱附件名ATT0001.bin问题解决:
修改 att1.add_header('Content-Disposition', 'attachment', filename='需要接收为的文件名.后缀')