利用smtplib和email實現SMTP發送郵件 V2.0

將之前利用smtplib和email實現SMTP發送郵件小程序進行重構。

發現了email.mime.multipart類兩個使用上的小問題:

  1. MIMEMultipart類要修改參數,不能直接使用賦值,必需使用replace_header(),不然將會是該參數的內容累加。比如:設置mail[‘Subject’]後,再一次賦值給mail[‘Subject’],結果是兩次賦值的內容累加。mail[‘From’]/mail[‘To’]也一樣的道理。
  2. 添加一個正文後,因爲已經編碼,修改起來很麻煩。最好是先刪除原正文MIMEText,後重新添加新的MIMEText。若是直接再次添加一個正文(MIMEText),那麼在郵件中會顯示多個正文內容的累加。MIMEMultipart類沒有提供刪除MIMEText的方法,但所有MIMEText都存放在MIMEMultipart類的_payload列表中。所以先找到正文的MIMEText對象,再使用列表的remove()方法來刪除。
說明:
  1. 考慮到有可能更換SMTP服務器,所以把SMTP服務器的設置信息用類來管理。
  2. 爲實現主題(mail[‘subject’])和正文(content)能直接賦值,使用了@property裝飾subject和content。
  3. 其中的Smtp類實現了上下文管理,可以使用with as。
  4. QQ的smtp登錄現在使用”CardDAV/CalDAV服務“,也就是你需要在QQ郵件的設置/帳戶裏獲取授權碼,用這個授權碼替代密碼。
  5. 未實現異常處理。----主要是我想不到怎麼處理 😦
使用方法如下:
from my_emailV2 import Email

from_email = '******@163.com'
to_email = '******@163.com'
# 要發多個郵箱時,請用","分隔開多個電子郵箱地址
# to_email = '******@163.com, ******@qq.com'

email = Email(from_email=from_email, to_email=to_email, smtp_server='qq')

email.subject = '測試郵件'
email.content = '郵件正文:這是一封測試郵件'
# 設置主題和正文也可使用以下方法:
# email.add_subject('測試郵件')
# email.add_content('郵件正文:這是一封測試郵件')

email.add_attachment('~/副本.txt')
email.add_attachment('~/test.py')
# 使用add_attachments()方法添加多個附件。
# email.add_attachments(['~/副本.txt', '~/test.py'])

email.send()
代碼如下:
# -*- coding: utf-8 -*-
"""
電子郵件類V2.0:用OO思想對V1.0版本進行重構。
"""
import smtplib
import os
import base64

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart


class Server163:
    host = 'smtp.163.com'
    user = '******@163.com'
    pwd = '******'


class ServerQQ:
    host = 'smtp.qq.com'
    user = '******@qq.com'
    pwd = '******' # CardDAV/CalDAV 的授權碼


server_config = {
    'default': ServerQQ,
    '163': Server163,
    'qq': ServerQQ
}


class Email(object):
    """
    電子郵件類
        爲實現mail.subject和mail.content直接賦值,使用了@property裝飾subject和content。
        MIMEMultipart類要修改參數,不能直接使用賦值,必需使用replace_header(),不然將會是該參數的內容累加。
            比如設置mail['Subject']後,再一次賦值給mail['Subject'],結果是兩次賦值的內容累加。mail['From']/mail['To']也一樣的道理。
        MIMEMultipart類的附件(正文也是一個附件)已編碼,因此直接修改起來很麻煩,最好是使用先刪除後重建的方法,詳見add_content()函數。
    :param: from_email:發件人地址
    :param: to_email:字串,收件人地址,多個收件人時用逗號隔開
    :param: subject:郵件主題
    :param: content:郵件正文,使用html
    :param: smtp_server:選擇郵件服務器

    Example:
        from_email = '******@163.com'
        to_email = '******@163.com'
        # 要發多個郵箱時,請用","分隔開多個電子郵箱地址
        # to_email = '******@163.com, ******@qq.com'

        email = Email(from_email=from_email, to_email=to_email, smtp_server='qq')

        email.subject = '測試郵件'
        email.content = '郵件正文:這是一封測試郵件'
        # 設置主題和正文也可使用以下方法:
        # email.add_subject('測試郵件')
        # email.add_content('郵件正文:這是一封測試郵件')

        email.add_attachment('~/副本.txt')
        email.add_attachment('~/test.py')
        # 使用add_attachments()方法添加多個附件。
        # email.add_attachments(['~/副本.txt', '~/test.py'])

        email.send()
    """

    def __init__(self, from_email, to_email, subject='無主題', content='', smtp_server='default'):
        self.server = smtp_server
        self.mail = MIMEMultipart()
        self.mail['From'] = from_email
        self.mail['To'] = to_email
        self.mail['Subject'] = subject

        if content:
            self.add_content(content)

    @property
    def subject(self):
        """爲了能使用直接賦值,使用了@property裝飾。但又覺得不必要返回原值,所以返回一個None"""
        return None

    @subject.setter
    def subject(self, value):
        self.add_subject(str(value))

    @property
    def content(self):
        """爲了能使用直接賦值,使用了@property裝飾。但又覺得不必要返回原值,所以返回一個None"""
        return None

    @content.setter
    def content(self, value):
        self.add_content(str(value))

    def add_subject(self, subject):
        """
        修改MIMEMultipart類的參數,只能使用replace_header(),不能使用直接賦值:self.mail['Subject'] = subject
        直接賦值是將新內容累加到原內容之上
        """
        self.mail.replace_header('Subject', subject)

    def add_content(self, content):
        """
        添加一個正文後,因爲已經編碼,修改起來很麻煩。就好是先刪除原正文MIMEText,後重新添加新的MIMEText。
        若是直接再次添加一個正文(MIMEText),那麼在郵件中會顯示多個正文內容的累加。
        MIMEMultipart類沒有提供刪除MIMEText的方法,但所有MIMEText都存放在MIMEMultipart類的_payload列表中。
        所以先找到正文的MIMEText對象,再使用列表的remove()方法來刪除。
        get_payload():列出所有的MIMEText對象。
        get_content_type():顯示該MIMEText對象的類型。text/html:文本;text/base64:文件
        """
        for payload in self.mail.get_payload():
            if payload.get_content_type() == 'text/html':
                self.mail._payload.remove(payload)
        self.mail.attach(MIMEText(content, _subtype='html', _charset='utf-8'))

    def add_attachment(self, filename):
        try:
            with open(filename, 'rb') as f:
                attachment = MIMEText(f.read(), 'base64', 'utf-8')
        except Exception as e:
            print(str(e))
            return False, '發送失敗:附件["{}"]讀取錯誤【{}】'.format(filename, str(e))
        else:
            attachment['Content-Type'] = 'application/octet-stream'
            file_name = os.path.split(filename)[1]
            # 下面一句是處理附件名是中文的情況
            file_name = '=?utf-8?b?' + base64.b64encode(file_name.encode()).decode() + '?='
            attachment["Content-Disposition"] = 'attachment; filename="%s"' % file_name
            self.mail.attach(attachment)

    def add_attachments(self, filename_list):
        for filename in filename_list:
            self.add_attachment(filename)

    def send(self):
        """兩種方式實現發送"""
        # 方法1:
        # smtp = Smtp(config=self.server, ssl=True)
        # smtp.send(self.mail)

        # 方法2:
        with Smtp(config=self.server, ssl=True) as smtp:
            smtp.send_mail(self.mail)


class Smtp(object):
    """
    郵件服務器類
        已實現上下文管理器功能,可使用with as。
    :param:config:郵件服務器的配置
    :param:ssl:是否創造帶SSL的郵件服務器

    Example1:
        smtp = Smtp(config='qq', ssl=True)
        smtp.send(mail)

    Example2:
        with Smtp(config=self.server, ssl=True) as smtp:
            smtp.send_mail(self.mail)
    """

    def __init__(self, config='default', ssl=False):
        self.server = server_config.get(config, 'default')
        self.server_host = self.server.host
        self.server_user = self.server.user
        self.sever_pwd = self.server.pwd
        self.ssl = ssl
        self.smtp = self.creat()

    def __enter__(self):
        self.login()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """這裏是否需要異常處理?"""
        self.quit()

    def creat(self):
        """通過ssl來判斷是否生成帶SSL的郵件服務器"""
        if self.ssl:
            return smtplib.SMTP_SSL(self.server_host, port=465)  # 設置郵件服務器
        else:
            return smtplib.SMTP(self.server_host, port=25)  # 設置郵件服務器

    def login(self):
        """登錄郵件服務器"""
        self.smtp.login(self.server_user, self.sever_pwd)  # 登陸郵件服務器

    def send_mail(self, mail):
        """發送郵件"""
        self.smtp.sendmail(self.server_user, mail['To'].split(','), mail.as_string())  # 發送郵件

    def quit(self):
        """退出郵件服務器"""
        self.smtp.quit()

    def send(self, mail):
        """整合創建/發送/退出爲一個函數"""
        self.login()
        self.send_mail(mail)
        self.quit()

如果本文對您有幫助,請給我留個言。謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章