Python_smtplib发送邮件/imaplib接收邮件
使用邮箱服务需要搭建smtp服务器,如果没有,可以使用第三方smtp服务器。
本文以第三方QQ邮箱服务器演示如何使用python的smtplib+email完成邮箱发送功能。
一、开启IMAP/SMTP服务并获取授权码
开启QQ邮箱SMTP服务
开启的最后一步是发送短信验证,获取 authorization。 QQ官方获取授权码的帮助文档。
使用SMTP服务有POP和IMAP(Internet Message Access Protocol)两种协议,我们选择使用IMAP,具体差异查看QQ邮箱帮助文档。
使用IMAP服务的SSL加密方式的通用配置如下:
接收邮件服务器:imap.qq.com,使用SSL,端口号993
发送邮件服务器:smtp.qq.com,使用SSL,端口号465或587
账户名:您的QQ邮箱账户名(如果您是VIP帐号或Foxmail帐号,账户名需要填写完整的邮件地址)
密码:您的QQ邮箱authorization
电子邮件地址:您的QQ邮箱的完整邮件地址
二、发送邮件
1、发送文本类型的邮箱
import smtplib from email.mime.text import MIMEText # 1.连接邮件服务器 smtpHost = "smtp.qq.com" # 邮件服务器地址 port = 465 # 邮件服务器端口 server = smtplib.SMTP_SSL(smtpHost, port) # 2.登录服务 sender = '418***167@qq.com' # 发件人邮箱账号 authorization = 'spi********idj' # QQ邮箱授权码 server.login(sender, authorization) # 括号中对应的是发件人邮箱账号、邮箱密码 # 3.构造邮件内容 # 3.1 创建邮箱容器 mailboxContainer = MIMEText('Hello Python!', "plain", "utf-8") # 创建文本类型容器 # 3.2 定义容器内容 mailboxContainer['Subject'] = "python发送的邮件" # 邮箱主题 mailboxContainer['From'] = sender # 邮箱发送人 receiver_to = ['y****@****.com'] mailboxContainer["To"] = ",".join(receiver_to) # 邮箱接收人 receiver_cc = [] mailboxContainer['Cc'] = ",".join(receiver_cc) # 邮箱抄送人 # 4.发送邮件 receiver = receiver_to + receiver_cc # 接收邮箱的人(包含接收和抄送) server.sendmail(sender, receiver, mailboxContainer.as_string()) # 5.关闭连接 server.quit()
收件结果如下:
2、发送HTML类型的邮件
import smtplib from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText # 1.连接邮件服务器 smtpHost = "smtp.qq.com" # 邮件服务器地址 port = 465 # 邮件服务器端口 server = smtplib.SMTP_SSL(smtpHost, port) # 2.登录服务 sender = '418XXXX167@qq.com' # 发件人邮箱账号 authorization = 'spiXXXXXXXidj' # QQ邮箱授权码 server.login(sender, authorization) # 括号中对应的是发件人邮箱账号、邮箱密码 # 3.构造邮件内容 # 3.1 创建邮箱容器 # mailboxContainer = MIMEText('邮件正文内容', "plain", "utf-8") # 创建文本类型容器 mailboxContainer = MIMEMultipart() # 创建混合类型容器 # 3.2 定义容器内容 mailboxContainer['Subject'] = "测试python发送邮件" # 邮箱主题 mailboxContainer['From'] = sender # 邮箱发送人 receiver_to = ['yXXo@XXXXX.com'] mailboxContainer["To"] = ",".join(receiver_to) # 邮箱接收人 receiver_cc = [] mailboxContainer['Cc'] = ",".join(receiver_cc) # 邮箱抄送人 # 混合类型,添加html内容 mail_msg = """ <p>Python 邮件发送测试...</p> <p>图片演示:</p> <p><img src="cid:image1"></p> """ mailboxContainer.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 读取图片信息 with open(r".\h5_img.jpg", "rb") as f: msg = f.read() msgImage = MIMEImage(msg) # 定义图片 ID,在 HTML 文本中引用 msgImage.add_header('Content-ID', '<image1>') mailboxContainer.attach(msgImage) # 4.发送邮件 receiver = receiver_to + receiver_cc # 接收邮箱的人(包含接收和抄送) server.sendmail(sender, receiver, mailboxContainer.as_string()) # 5.关闭连接 server.quit()
接收结果如下:
3、发送带附件的邮件
import smtplib from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText # 1.连接邮件服务器 smtpHost = "smtp.qq.com" # 邮件服务器地址 port = 465 # 邮件服务器端口 server = smtplib.SMTP_SSL(smtpHost, port) # 2.登录服务 sender = '41XXXX7@qq.com' # 发件人邮箱账号 authorization = 'spXXXXXXdj' # QQ邮箱授权码 server.login(sender, authorization) # 括号中对应的是发件人邮箱账号、邮箱密码 # 3.构造邮件内容 # 3.1 创建邮箱容器 # mailboxContainer = MIMEText('邮件正文内容', "plain", "utf-8") # 创建文本类型容器 mailboxContainer = MIMEMultipart() # 创建混合类型容器 # 3.2 定义容器内容 mailboxContainer['Subject'] = "带附件的邮箱" # 邮箱主题 mailboxContainer['From'] = sender # 邮箱发送人 receiver_to = ['yXXX@XXXXX.com'] mailboxContainer["To"] = ",".join(receiver_to) # 邮箱接收人 receiver_cc = [] mailboxContainer['Cc'] = ",".join(receiver_cc) # 邮箱抄送人 # # 混合类型,添加文本内容 mailboxContainer.attach(MIMEText('测试带附件的邮箱', "plain", "utf-8")) # 构造文本附件 with open(r".\text.txt", "rb") as f: msg = f.read() att1 = MIMEText(msg, 'base64', 'utf-8') att1["Content-Type"] = 'application/octet-stream' att1["Content-Disposition"] = 'attachment; filename="text.txt"' # 这里的filename可以任意写,写什么名字,邮件附件中显示什么名字 mailboxContainer.attach(att1) # 构造图片附件 with open(r".\h5_img.jpg", "rb") as f: msg = f.read() att2 = MIMEText(msg, 'base64', 'utf-8') att2["Content-Type"] = 'application/octet-stream' # att2["Content-Disposition"] = 'attachment; filename="h5_img"' # 这里的filename可以任意写,写什么名字,邮件中显示什么名字 att2.add_header('content-disposition', 'attachment', filename='h5.jpg') # 与上面注释代码功能一样 mailboxContainer.attach(att2) # 4.发送邮件 receiver = receiver_to + receiver_cc # 接收邮箱的人(包含接收和抄送) server.sendmail(sender, receiver, mailboxContainer.as_string()) # 5.关闭连接 server.quit()
接收结果如下:
4、初步封装
import os import smtplib from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from utils.file import file class SMTP: def __init__(self, smtp_host="smtp.qq.com", port=465): # 连接邮件服务器 self.__server = smtplib.SMTP_SSL(smtp_host, port) self.__sender = None # 发件人邮箱账号 self.__mailboxContainer = MIMEMultipart() # 创建邮箱容器 self.__receiver = [] def quit(self): self.__server.quit() def login(self, account, authorization): """登录邮箱服务器""" self.__sender = account self.__server.login(account, authorization) # 括号中对应的是发件人邮箱账号、邮箱密码 def add_subject(self, subject): """添加邮件主题""" self.__mailboxContainer['Subject'] = subject # 邮箱主题 def add_receiver(self, receiver_to: list, receiver_cc: list = None): """ 添加邮件接收人 receiver_to:收件人 receiver_cc:抄送人 """ self.__mailboxContainer["From"] = self.__sender self.__mailboxContainer["To"] = ",".join(receiver_to) # 邮箱接收人 self.__mailboxContainer['Cc'] = ",".join(receiver_cc) # 邮箱抄送人 self.__receiver = receiver_to + receiver_cc def add_content(self, content, mail_type="plain", append_imgs: list = None): """ 添加邮箱内容 content:邮箱内容 mail_type:内容的类型 append_imgs:当为html类型时追加图片内容 """ if mail_type != "html" and append_imgs is not None: raise ValueError(f"main_type的值不为html,但append_img不是空") if mail_type == "html" and append_imgs is not None: for append_img in append_imgs: img_tag = f"<p><img src='cid:image{append_imgs.index(append_img)}'></p>" content += img_tag # 读取图片信息 with open(append_img, "rb") as f: msg = f.read() msgImage = MIMEImage(msg) # 定义图片 ID,在 HTML 文本中引用 msgImage.add_header('Content-ID', f'<image{append_imgs.index(append_img)}>') self.__mailboxContainer.attach(msgImage) self.__mailboxContainer.attach(MIMEText(content, mail_type, "utf-8")) def add_attach(self, file_path): """添加单个附件""" if not os.path.exists(file_path): raise ValueError(f"文件【{file_path}】不存在") if not os.path.isfile(file_path): raise ValueError(f"【{file_path}】不是文件") # 构造文本附件 with open(file_path, "rb") as f: msg = f.read() att = MIMEText(msg, 'base64', 'utf-8') att["Content-Type"] = 'application/octet-stream' filename = file.get_path_last_name(file_path) att["Content-Disposition"] = f'attachment; filename="{filename}"' # 这里的filename可以任意写,写什么名字,邮件附件中显示什么名字 self.__mailboxContainer.attach(att) def add_attachs(self, file_paths: list): """添加多个附件""" for file_path in file_paths: self.add_attach(file_path) def send(self): """发送邮件""" self.__server.sendmail(self.__sender, self.__receiver, self.__mailboxContainer.as_string()) if __name__ == '__main__': mailbox = SMTP() # 登录邮箱服务器 account = '418XXXX167@qq.com' authorization = 'spiXXXXXidj' # QQ邮箱授权码 mailbox.login(account, authorization) # 添加邮箱主题 mailbox.add_subject("使用SMTP封装类发送的邮件") # 添加邮箱接收人 receiver_to = ['yXXXo@XXX.com'] # 收件人 receiver_cc = ['41XXX7@qq.com'] # 抄送人 mailbox.add_receiver(receiver_to, receiver_cc) # 添加邮箱内容 mail_content = """ <p>Python 邮件发送测试...</p> <p>追加图片如下</p> """ append_imgs = [r".\h5_img.jpg", r".\doudou.png"] mailbox.add_content(mail_content, mail_type="html", append_imgs=append_imgs) # 添加附件 mailbox.add_attach("text.txt") mailbox.add_attachs([r".\h5_img.jpg", r".\doudou.png"]) # 发送邮箱 mailbox.send()
发送结果如下
三、接收邮件
1、imaplib方法介绍
import email import imaplib import quopri import re from email.header import decode_header imap_host = "imap.163.com" email_user = "XXXX@163.com" email_pwd = "BJQMDXXXXJRSQEB" imap_client = imaplib.IMAP4(imap_host) imap_client.login(email_user, email_pwd) # 解决网易邮箱报错:Unsafe Login. Please contact kefu@188.com for help imaplib.Commands["ID"] = ('AUTH',) args = ("name", email_user, "contact", email_user, "version", "1.0.0", "vendor", "myclient") imap_client._simple_command("ID", str(args).replace(",", "").replace("\'", "\"")) # 获取邮箱目录。INBOX(收件箱)/Drafts(草稿箱)/Junk(垃圾箱)/Trash(已删除)/Sent Messages(已发送) mail_dir = imap_client.list() print(mail_dir) # 选择邮箱。返回的数据是中的消息数 信箱 (EXISTS 反应)。默认值信箱是'INBOX' imap_client.select() # 在邮箱中搜索邮件。ALL(全部邮件),UNSEEN(未读邮件),SEEN(已读邮件) # typ:搜索结果状态 # dat:邮件的索引号 typ, dat = imap_client.search(None, "ALL") print(typ, dat) # 查看第 mail_index 封邮件详情 mail_index = "2" # typ:搜索结果状态 # dat:邮件的索引号 typ, dat = imap_client.fetch(mail_index, '(RFC822)') print(typ, dat) mail_data = dat[0][1] print(mail_data) print("========= 获取邮件头部信息 ===============") # 将字节字符串解析为 Message 对象模型。 message = email.message_from_bytes(mail_data) print(decode_header(message["Subject"])[0][0].decode("utf-8")) # 邮件日期 print(decode_header(message["Date"])[0][0]) # 邮件发送人 print(decode_header(message["From"])[0][0].decode("utf-8")) # 邮件接收人 print(decode_header(message["To"])[0][0]) print("========= 获取邮件正文信息 ===============") ret = re.search(b"<body>[\s|\S]*</body>", mail_data) content = ret.group() print(content) # 将邮件中的MIME字符进行解码 content = quopri.decodestring(content) print(content.decode())
注:使用imaplib方法时,若是163的邮箱会报错:Unsafe Login. Please contact kefu@188.com for help,参考 https://blog.csdn.net/jony_online/article/details/108638571 解决问题
执行结果
2、poplib方法介绍
# coding=utf-8 import poplib from email.parser import Parser import quopri from email.header import decode_header pop_host = "pop.163.com" email_user = "XXXX@163.com" email_pwd = "XXXX" pop3_client = poplib.POP3(pop_host) pop3_client.user(email_user) pop3_client.pass_(email_pwd) # 获取邮件数量和占用空间 mail_count, mailbox_size = pop3_client.stat() print(mail_count, mailbox_size) # 获取邮件列表。 # response:获取状态 # mail_list:list类型,每个元素为邮件索引和大小 # octets:为mails_info每个元素长度的总和 response, mail_list, octets = pop3_client.list() print(response, mail_list, octets) # 获取邮件详情。【第一封邮件的索引为1】 mail_index = 1 # response:获取状态 # mail_detail_list:邮件详情。以list类型保存每行内容 # octets:为mail_detail_list每个元素长度的总和 response, mail_detail_list, octets = pop3_client.retr(mail_index) # print(response, mail_detail_list, octets) print(mail_detail_list) # 关闭POP3服务 pop3_client.quit() # 提取正文内容 t = 0 contents = [] for i in mail_detail_list: if "<body>" in i: t = 1 if t == 1: contents.append(i) if "</body>" in i: t = 0 # 拼接邮箱详情内容 content = "\r\n".join(contents).decode("utf-8") # 将邮件中的MIME内容进行解码 content = quopri.decodestring(content) print(content) join_detail_str = "\r\n".join(mail_detail_list).decode("utf-8") # print(join_detail_str) # 生成对象信息 content_object = Parser().parsestr(join_detail_str) print(content_object.keys()) # 解析header内容不能用quopri,会出现乱码 # 邮件标题 subject = decode_header(content_object["Subject"])[0][0] print(subject) # 邮件日期 print(decode_header(content_object["Date"]))[0][0] # 邮件发送人 print(decode_header(content_object["From"]))[0][0] # 邮件接收人 print(decode_header(content_object["To"]))[0][0]
执行结果
3、imaplib的基础封装
import email import imaplib import quopri import re import time from email.header import decode_header class Imap: def __init__(self, email_host, email_user, email_pwd): self.imap_client = imaplib.IMAP4(email_host) self.imap_client.login(email_user, email_pwd) # 解决网易邮箱报错Unsafe Login. Please contact kefu@188.com for help imaplib.Commands["ID"] = ('AUTH',) args = ("name", email_user, "contact", email_user, "version", "1.0.0", "vendor", "myclient") self.imap_client._simple_command("ID", str(args).replace(",", "").replace("\'", "\"")) def get_email(self, mail_index=None, mail_status="ALL", mailbox="INBOX"): """获取邮箱内容 mail_index: 邮箱序号,从1开始 mailbox: 获取邮箱的目录。INBOX(收件箱)/Drafts(草稿箱)/Junk(垃圾箱)/Trash(已删除)/Sent Messages(已发送) mail_status:获取邮箱的状态。ALL(全部邮件),UNSEEN(未读邮件),SEEN(已读邮件) """ # 选择邮箱。返回的数据是中的消息数 信箱 (EXISTS 反应)。默认值信箱是'INBOX' self.imap_client.select(mailbox) # 在邮箱中搜索邮件。 _, dat = self.imap_client.search(None, mail_status) if not dat[0]: raise RuntimeError("没有获取到邮件") # 未指定获取邮件的序号,则为最后一封邮件 if mail_index is None: mail_index = dat[0].decode("utf-8").split(" ")[-1] mail_index = str(mail_index) # 查看第 mail_index 封邮件详情 _, dat = self.imap_client.fetch(mail_index, '(RFC822)') dat_obj = ParseEmail(dat[0][1]) return dat_obj class ParseEmail: """ 解析邮件内容""" def __init__(self, dat): self.dat = dat # 将字节字符串解析为 Message 对象模型。 self.message = email.message_from_bytes(dat) @property def subject(self): """ 邮件标题 """ return decode_header(self.message["Subject"])[0][0].decode("utf-8") @property def date(self): """ 邮件接收日期 """ str_time = decode_header(self.message["Date"])[0][0] time_struct = time.strptime(str_time, '%a, %d %b %Y %H:%M:%S +0800') _time = time.strftime('%Y-%m-%d %H:%M:%S', time_struct) return _time @property def sender(self): """ 邮件发送人 """ return decode_header(self.message["From"])[0][0] @property def receiver(self): """ 邮件接收人 """ return decode_header(self.message["To"])[0][0] @property def content_body(self): """ 获取html邮件类型的body内容 """ ret = re.search(b"<body>[\s|\S]*</body>", self.dat) if not ret: raise RuntimeError("邮件不是html类型") content = ret.group() # 将邮件中的MIME字符进行解码 content = quopri.decodestring(content) return content.decode() if __name__ == '__main__': email_host = "imap.163.com" email_user = "XXXXXX@163.com" email_pwd = "XXXX" imap = Imap(email_host, email_user, email_pwd) email_content = imap.get_email(mail_index=1) print(email_content.subject) print(email_content.date) print(email_content.content_body)