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]
poplib代码

执行结果

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)

 

 

 

posted @ 2021-03-17 11:30  码上测  阅读(1879)  评论(0编辑  收藏  举报