python3之發(fā)送和讀取郵件

一晾腔、發(fā)送郵件

  • 發(fā)送郵件使用SMTP協(xié)議【Simple Mail Transfer Protocol簡單的郵件傳輸協(xié)議】剔应,SMTP協(xié)議是SMTP客戶端與SMTP服務(wù)器之間的通信協(xié)議。
  • python中發(fā)送郵件使用的模塊有smtplib和email:
    使用smtplib模塊進(jìn)行發(fā)送郵件峻贮;
    使用email模塊來添加發(fā)送的郵件內(nèi)容。

1. smtplib模塊

導(dǎo)入模塊:import smtplib

1.1. 創(chuàng)建SMTP對象

smtplib.SMTPsmtplib.SMTP_SSL:均可以用來創(chuàng)建SMTP對象嚼黔;
smtplib.SMTP_SSL:使用安全加密的SSL協(xié)議連接到SMTP服務(wù)器唬涧;
smtplib.SMTP:沒有進(jìn)行安全加密碎节。
故若待測試郵箱不允許使用非SSL和非TLS頻道通信時捧搞,則無法使用smtp.SMTP方式來創(chuàng)建客戶端對象雅采。
【查看郵箱的通信方式:郵箱設(shè)置菜單中婚瓜,查看郵箱的接收服務(wù)器和發(fā)送服務(wù)器信息胡陪∮匏恚】

如:騰訊企業(yè)郵箱
接收服務(wù)器:
imap.exmail.qq.com(使用SSL廊营,端口號993)
發(fā)送服務(wù)器:
smtp.exmail.qq.com(使用SSL,端口號465)
  • smtplib.SMTP(host, port, local_hostname, timeout, source_address)
  • smtplib.SMTP_SSL(host, port, local_hostname, keyfile, certfile, timeout, source_address, context)
    創(chuàng)建SMTP對象蜗巧。
    host:SMTP發(fā)送服務(wù)器主機(jī)
    port:SMTP服務(wù)器端哭口號
1.2. SMTP對象操作
  • login(user, password, *, initial_response_ok=True)
    SMTP對象登錄
    user:授權(quán)登錄的用戶名
    password:授權(quán)登錄的密碼
  • sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
    SMTP對象發(fā)送郵件
    from_addr:發(fā)件人地址掌眠,字符串類型。
    to_addr:收件人地址惧蛹,包括收件人和抄送人扇救。
    多個收件人時to_addr參數(shù)為列表,單個收件人時to_addr參數(shù)可以為列表或字符串香嗓。
    msg:要發(fā)送的信息
  • quite()
    終止SMTP會話

2. 發(fā)送郵件的實(shí)例

2.1. 添加郵件內(nèi)容迅腔,包括收件人、抄送人靠娱、正文沧烈、附件
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import os
class EmailContent:
   
    def __init__(self, senderAdr, emailSubject, toReceivers, ccReceivers):
        # 郵件對象
        self.msg = MIMEMultipart()
        # 添加發(fā)件人頭
        self.msg['From'] = Header("測試" + "<" + senderAdr + ">", 'utf-8')
        # 添加收件人
        if isinstance(toReceivers, str):
            self.msg["To"] = toReceivers
        elif isinstance(toReceivers, list):
            self.msg['To'] = ";".join(toReceivers)
        # 添加抄送人
        if isinstance(ccReceivers, str):
            self.msg["Cc"] = ccReceivers
        elif isinstance(ccReceivers, list):
            self.msg["Cc"] = ";".join(ccReceivers)
        # 添加郵件主題
            self.msg['Subject'] = Header(emailSubject, "utf-8")

    def addBody(self, bodyType):
        """
        添加不同的郵件正文的實(shí)例
        1. body為字符串:(如)"這是一個郵件正文內(nèi)容"
        2. body為html格式的字符串:(如)"<div><p>第一段</p><p>&nbsp;第二段</p></div>"
        3. body正文中包含有圖片:
        """
        if bodyType == "string":
            body = "這是一個郵件正文內(nèi)容"
            mimeText = MIMEText(body, "plain", "utf-8")
            self.msg.attach(mimeText)
        elif bodyType == "html":
            body = "<div><p>第一段</p><p>&nbsp;第二段</p></div>"
            mimeText = MIMEText(body, "html", "utf-8")
            self.msg.attach(mimeText)
        elif "image" in bodyType:
            imageFile = "E://log//test.png"
            imageId = os.path.split(imageFile)[1]
            # 添加內(nèi)容
            body = '''
                    <p>測試圖片為:</p>
                    <p><img src="cid:{imageId}"></p>
                    '''.format(imageId=imageId)
            mimeText = MIMEText(body, "html", "utf-8")
            self.msg.attach(mimeText)
            # 讀取圖片,并設(shè)置圖片id用于郵件正文引用
            with open(imageFile, "rb") as fp:
                mimeImage = MIMEImage(fp.read())
            mimeImage.add_header("Content-ID", imageId)
            self.msg.attach(mimeImage)

        def addAttachment(self, attachmentName):
        """
        添加附件
        :return:
        """
        file = "E://log//test.txt"
        # file = "E://log//test.zip"
        # file = "E://log//test.png"
        filePath, fileName = os.path.split(file)
        print("fileName =", fileName)
        enclosure = MIMEText(open(file, 'rb').read(), 'base64', 'utf-8')
        enclosure['Content-Type'] = 'application/octet-stream'
        if attachmentName == "英文":
            enclosure['Content-Disposition'] = 'attachment; filename="%s"' % fileName
        elif attachmentName == "中文":
            enclosure.add_header("Content-Disposition", "attachment", filename=("gbk", "", fileName))
        self.msg.attach(enclosure)

2.2. 發(fā)送郵件
import smtplib
def SendEmail():
    """發(fā)送郵件"""
    # SMTP的服務(wù)器信息
    smtpHost = "smtp.exmail.qq.com"
    sslPort = 465
    senderAdr = "xx@xx.cn"
    senderPwd = "XXXX"
    # 創(chuàng)建SMTP對象
    smtpServer = smtplib.SMTP_SSL(smtpHost, sslPort)
    # # 設(shè)置debug模塊
    # smtpServer.set_debuglevel(True)
    # 登錄
    smtpServer.login(senderAdr, senderPwd)
    # 添加郵件內(nèi)容
    toReceivers = ["a@xx.cn", "b@xx.cn"]
    ccReceivers = ["d@xx.cn", "e@xx.cn"]
    toAddrs = toReceivers + ccReceivers
    emailSubject = "這是個自動發(fā)送的郵件"
    emailContent = EmailContent(senderAdr, emailSubject, toReceivers, ccReceivers)
    emailContent.addBody("html")
    emailContent.addAttachment("英文")
    message = emailContent.msg
    # 發(fā)送
    smtpServer.sendmail(senderAdr, toAddrs, message.as_string())
    # 終止SMTP會話
    smtpServer.quit()

SendEmail()

二像云、讀取郵件

  • 收取郵件使用POP3協(xié)議锌雀;
  • 解析郵件:需要將收取的郵件轉(zhuǎn)化為email.message.Message對象,再使用email模塊解析內(nèi)容迅诬。

1. 讀取郵件的實(shí)例

1.1. 獲取某封郵件的對象
import poplib
from email.parser import Parser
"""POP的服務(wù)器信息"""
popHost = "pop.exmail.qq.com"
userAdr = "xx@xx.cn"
userPwd = "xxxxx"

""" 創(chuàng)建POP3對象腋逆,添加用戶名和密碼"""
pop3Server = poplib.POP3(popHost)
pop3Server.user(userAdr)
pop3Server.pass_(userPwd)

"""獲取郵件數(shù)量和占用空間"""
messageCount, mailboxSize = pop3Server.stat()

"""獲取郵件請求返回狀態(tài)碼、每封郵件的字節(jié)大小(b'第幾封郵件 此郵件字節(jié)大小')侈贷、"""
response, msgNumOctets, octets = pop3Server.list()

""" 獲取任意一封郵件的郵件對象【第一封郵件的編號為1惩歉,而不是0】"""
msgIndex = random.randint(1,messageCount)
print(msgIndex)
# 獲取第msgIndex封郵件的信息
response, msgLines, octets = pop3Server.retr(msgIndex)
# msgLines中為該郵件的每行數(shù)據(jù),先將內(nèi)容連接成字符串,再轉(zhuǎn)化為email.message.Message對象
msgLinesToStr = b"\r\n".join(msgLines).decode("utf8", "ignore")
messageObject = Parser().parsestr(msgLinesToStr)
print(messageObject)

""" 終止POP3服務(wù)"""
pop3Server.quit()
1.2. 解析郵件對象
1.2.1. 獲取郵件日期
msgDate = messageObject["date"]
print(msgDate)
1.2.2. 獲取郵件發(fā)件人實(shí)名、郵箱地址

獲取郵件實(shí)名時撑蚌,名稱一般是加密的上遥,此時就需要對頭文件進(jìn)行解碼才可獲取它的實(shí)際內(nèi)容

from email.header import decode_header
def decodeMsgHeader(header):
    """
    解碼頭文件
    :param header: 需解碼的內(nèi)容
    :return:
    """
    value, charset = decode_header(header)[0]
    if charset:
        value = value.decode(charset)
    return value
from email.utils import parseaddr
senderContent = messageObject["From"]
# parseaddr()函數(shù)返回的是一個元組(realname, emailAddress)
senderRealName, senderAdr = parseaddr(senderContent) 
# 將加密的名稱進(jìn)行解碼 
senderRealName = decodeMsgHeader(senderRealName)   
print(senderRealName)
print(senderAdr)
1.2.3. 獲取郵件主題

獲取的郵件的主題也是加密的,此時就需要對頭文件進(jìn)行解碼才可獲取它的實(shí)際內(nèi)容

msgHeader = messageObject["Subject"]
# 對頭文件進(jìn)行解碼
msgHeader = decodeMsgHeader(msgHeader )
print(msgHeader)
1.2.4. 獲取郵件正文

一封郵件的正文內(nèi)容争涌,可能是由幾部分構(gòu)成粉楚,每部分的格式不同。

"""獲取郵件正文內(nèi)容"""
msgBodyContents = []
if messageObject.is_multipart():  # 判斷郵件是否由多個部分構(gòu)成
    messageParts = messageObject.get_payload()  # 獲取郵件附載部分
    for messagePart in messageParts:
        bodyContent = decodeBody(messagePart)
        if bodyContent:
            msgBodyContents.append(bodyContent)
else:
    bodyContent = decodeBody(messageObject)
    if bodyContent:
        messageBodyContents.append(bodyContent)
print(msgBodyContents)
def decodeBody(msgPart):
    """
    解碼內(nèi)容
   :param msgPart: 郵件某部分
    """
    contentType = msgPart.get_content_type()  # 判斷郵件內(nèi)容的類型,text/html
    textContent = ""
    if contentType == 'text/plain' or contentType == 'text/html':
        content = msgPart.get_payload(decode=True)
        charset = msgPart.get_charset()
        if charset is None:
            contentType = msgPart.get('Content-Type', '').lower()
            position = contentType.find('charset=')
            if position >= 0:
                charset = contentType[position + 8:].strip()
        if charset:
            textContent = content.decode(charset)
    return textContent
1.2.5. 獲取郵件附件

郵件附件名為中文時亮垫,需借助頭文件解碼方式進(jìn)行解碼模软,否則會為亂碼。

messageAttachments = []
if messageObject.is_multipart():  #   判斷郵件是否由多個部分構(gòu)成
    messageParts = messageObject.get_payload()  # 獲取郵件附載部分
    for messagePart in messageParts:
        name = messagePart.get_param("name")  # 名字存在包警,則表示此部分為附件
        if name:
            fileName = decodeMsgHeader(name)   # 解碼
            messageAttachments.append(fileName)
else:
    name = messageObject.get_param("name")
    if name:
       fileName = decodeMsgHeader(name)   # 解碼
       messageAttachments.append(fileName)
print(messageAttachments)

2. 讀取郵件時遇到的問題

2.1. 提示“poplib.error_proto: line too long”

File "XXX/EmailInfo.py", line 22, in getMessageObject
    return parser.Parser().parsestr(b"\n".join(self.popServer.retr(i)[1]).decode("utf8", "ignore"))
  File "/usr/local/lib/python3.6/poplib.py", line 248, in retr
    return self._longcmd('RETR %s' % which)
  File "/usr/local/lib/python3.6/poplib.py", line 183, in _longcmd
    return self._getlongresp()
  File "/usr/local/lib/python3.6/poplib.py", line 168, in _getlongresp
    line, o = self._getline()
  File "/usr/local/lib/python3.6/poplib.py", line 130, in _getline
    raise error_proto('line too long')
poplib.error_proto: line too long

POP3對行長度做了限制撵摆,默認(rèn)為_MAXLINE = 2048,故若是郵件超過此長度就會提示“poplib.error_proto: line too long”害晦。
解決方案:在讀取郵件代碼中重新定義最大行長度特铝,即給poplib._MAXLINE設(shè)置新值。

import poplib
poplib._MAXLINE=20480
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壹瘟,一起剝皮案震驚了整個濱河市鲫剿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稻轨,老刑警劉巖灵莲,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異殴俱,居然都是意外死亡政冻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門线欲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來明场,“玉大人,你說我怎么就攤上這事李丰】嘞牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵趴泌,是天一觀的道長舟舒。 經(jīng)常有香客問我,道長嗜憔,這世上最難降的妖魔是什么秃励? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮吉捶,結(jié)果婚禮上莺治,老公的妹妹穿的比我還像新娘廓鞠。我一直安慰自己,他們只是感情好谣旁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滋早,像睡著了一般榄审。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上杆麸,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天搁进,我揣著相機(jī)與錄音,去河邊找鬼昔头。 笑死饼问,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的揭斧。 我是一名探鬼主播莱革,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼讹开!你這毒婦竟也來了盅视?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旦万,失蹤者是張志新(化名)和其女友劉穎闹击,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體成艘,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赏半,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了淆两。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片断箫。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖琼腔,靈堂內(nèi)的尸體忽然破棺而出瑰枫,到底是詐尸還是另有隱情,我是刑警寧澤丹莲,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布光坝,位于F島的核電站,受9級特大地震影響甥材,放射性物質(zhì)發(fā)生泄漏盯另。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一洲赵、第九天 我趴在偏房一處隱蔽的房頂上張望鸳惯。 院中可真熱鬧商蕴,春花似錦、人聲如沸芝发。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辅鲸。三九已至格郁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間独悴,已是汗流浹背例书。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刻炒,地道東北人决采。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像坟奥,于是被迫代替她去往敵國和親树瞭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355