21 Python發(fā)送和接收電子郵件

郵件是我們?nèi)粘9ぷ髦兄饕臏贤浇橹弧D壳皫缀跛芯幊陶Z(yǔ)言都支持發(fā)送和接收電子郵件。
接下來(lái)將介紹如何使用Python語(yǔ)言發(fā)送和接收郵件举哟。

電子郵件介紹

Email(電子郵件)的歷史比Web還要久遠(yuǎn)俯渤。直到現(xiàn)在僧界,Email還是互聯(lián)網(wǎng)上應(yīng)用非常廣泛的服務(wù)侨嘀。
在我們開(kāi)始編寫(xiě)郵件操作的相關(guān)代碼之前,先了解一下電子郵件在互聯(lián)網(wǎng)上是如何運(yùn)作的捂襟。
電子郵件其實(shí)是我們現(xiàn)實(shí)生活中快遞的電子化飒炎,現(xiàn)實(shí)中快遞是怎么處理的呢?比如你在上海笆豁,要郵寄一份文件給北京的朋友郎汪。
首先需要準(zhǔn)備好郵寄的文件,選擇一家快遞公司(一般是上門(mén)取件或到代理點(diǎn)投遞)闯狱,快遞公司會(huì)提供對(duì)應(yīng)的信封煞赢,在信封上填寫(xiě)地址,剩下的事就由快遞公司處理了哄孤。
快遞公司會(huì)將一個(gè)地點(diǎn)的信件從就近的小代理點(diǎn)匯聚到一個(gè)快遞中心照筑,再?gòu)目爝f中心往別的城市發(fā),比如先發(fā)到河南某城市的快遞中心瘦陈,再?gòu)脑撎幇l(fā)往北京凝危,也可能由上海直達(dá)北京,不過(guò)你不用關(guān)心具體路線晨逝,只需要知道一件事蛾默,就是信件走得比較慢,至少要幾天時(shí)間捉貌。
信件到達(dá)北京的快遞中心后不會(huì)直接送到朋友的手里支鸡。快遞員為了避免你的朋友不在趁窃,而讓自己白跑一趟牧挣,會(huì)將信件投遞到郵件指定的地址,這個(gè)地址可能是你朋友居住附近的快遞箱醒陆、家里或所在公司瀑构。總之刨摩,當(dāng)你的朋友知道自己的信件已經(jīng)到達(dá)時(shí)寺晌,就可以取到信件了。
電子郵件的流程基本上是按上面的方式運(yùn)作的码邻,只不過(guò)速度不是按天算折剃,而是按秒算。
現(xiàn)在回到電子郵件像屋,假設(shè)自己的電子郵件地址是me@163.com怕犁,對(duì)方的電子郵件地址是friend@aliyun.com。用Outlook或Foxmail之類(lèi)的軟件寫(xiě)好郵件己莺,填上對(duì)方的Email地址奏甫,單擊“發(fā)送”按鈕,電子郵件就發(fā)送出去了凌受。這些電子郵件被稱(chēng)為郵件用戶(hù)代理(Mail User Agent, MUA)阵子。
Email從MUA發(fā)出去后,不是直接到達(dá)對(duì)方電腦胜蛉,而是發(fā)到郵件傳輸代理(Mail Transfer Agent挠进,MTA)色乾,就是Email服務(wù)提供商,如網(wǎng)易领突、阿里云等暖璧。由于自己的電子郵件地址是163.com,因此Email首先被投遞到網(wǎng)易提供的MTA君旦,再由網(wǎng)易的MTA發(fā)送到對(duì)方的服務(wù)商澎办,也就是阿里的MTA。在這個(gè)過(guò)程中可能還會(huì)經(jīng)過(guò)別的MTA金砍,但是我們不用關(guān)心具體路線局蚀,只關(guān)心速度即可。
Email到達(dá)阿里的MTA后恕稠,由于對(duì)方使用的是@aliyun.com的郵箱琅绅,因此阿里的MTA會(huì)把Email投遞到郵件的最終目的地郵件投遞代理(Mail Delivery Agent, MDA)。Email到達(dá)MDA后谱俭,會(huì)存放在阿里云服務(wù)器的某個(gè)文件或特殊的數(shù)據(jù)庫(kù)里奉件,我們將這個(gè)長(zhǎng)期保存郵件的地方稱(chēng)之為電子郵箱。
同普通郵件類(lèi)似昆著,Email不會(huì)直接到達(dá)對(duì)方的電腦县貌,因?yàn)閷?duì)方的電腦不一定開(kāi)機(jī),開(kāi)機(jī)也不一定聯(lián)網(wǎng)凑懂。對(duì)方要取到郵件煤痕,必須通過(guò)MUA從MDA上獲得。
一封電子郵件的旅程是:
發(fā)件人→MUA→MTA→MTA→若干個(gè)MTA→MDA←MUA←收件人
了解了上述基本概念接谨,要編寫(xiě)程序發(fā)送和接收郵件摆碉,本質(zhì)就是:
(1)編寫(xiě)MUA把郵件發(fā)到MTA。
(2)編寫(xiě)MUA從MDA上收郵件脓豪。
發(fā)郵件時(shí)巷帝,MUA和MTA使用的協(xié)議是SMTP(Simple Mail Transfer Protocol),后面的MTA到另一個(gè)MTA也是用SMTP協(xié)議扫夜。
收郵件時(shí)楞泼,MUA和MDA使用的協(xié)議有兩種:一種是POP(Post Office Protocol),目前版本是3笤闯,俗稱(chēng)POP3堕阔;另一種是IMAP(Internet Message Access Protocol),目前版本是4颗味,優(yōu)點(diǎn)是不但能取郵件超陆,而且可以直接操作MDA上存儲(chǔ)的郵件,如從收件箱移到垃圾箱等浦马。
郵件客戶(hù)端軟件在發(fā)郵件時(shí)时呀,會(huì)讓你先配置SMTP服務(wù)器张漂,就是要發(fā)到哪個(gè)MTA上。假設(shè)你正在使用163郵箱谨娜,就不能直接發(fā)到阿里的MTA上鹃锈,因?yàn)樗环?wù)于阿里的用戶(hù),所以需要填寫(xiě)163提供的SMTP服務(wù)器地址smtp.163.com瞧预。為了證明你是163的用戶(hù),SMTP服務(wù)器還要求你填寫(xiě)郵箱地址和客戶(hù)端授權(quán)密碼仅政,這樣MUA才能正常把Email通過(guò)SMTP協(xié)議發(fā)送到MTA垢油。
同樣,從MDA收郵件時(shí)圆丹,MDA服務(wù)器也要求驗(yàn)證你的客戶(hù)端授權(quán)密碼滩愁,確保不會(huì)有人冒充你收取郵件。一般Outlook之類(lèi)的郵件客戶(hù)端會(huì)要求填寫(xiě)POP3或IMAP服務(wù)器地址辫封、郵箱地址和授權(quán)密碼硝枉。這樣,MUA才能順利通過(guò)POP或IMAP協(xié)議倦微,從MDA取到郵件妻味。
在使用Python收發(fā)郵件前,需要先準(zhǔn)備好至少兩個(gè)電子郵件欣福,如xxx@163.com责球,xxx@aliyun.comxxx@qq.com等拓劝,注意兩個(gè)郵箱不要用同一家郵件服務(wù)商雏逾。
最后特別注意,目前大多數(shù)郵件服務(wù)商都需要手動(dòng)打開(kāi)SMTP發(fā)信和POP收信功能郑临,否則只允許在網(wǎng)頁(yè)登錄栖博。

發(fā)送郵件

SMTP是發(fā)送郵件的協(xié)議,Python內(nèi)置對(duì)SMTP的支持厢洞,可以發(fā)送純文本郵件仇让、HTML郵件以及帶附件的郵件。本節(jié)以網(wǎng)易163的服務(wù)為例
進(jìn)行介紹犀变。學(xué)習(xí)本節(jié)內(nèi)容時(shí)妹孙,可以自己開(kāi)通對(duì)應(yīng)的郵箱服務(wù),各個(gè)郵件服務(wù)公司有介紹郵箱服務(wù)的開(kāi)通方法获枝,參照這些開(kāi)通方法開(kāi)通即可蠢正。如果已經(jīng)安裝了郵箱服務(wù),就可以使用自己的郵箱服務(wù)器進(jìn)行學(xué)習(xí)省店。

1 SMTP發(fā)送郵件

Python對(duì)SMTP的支持有smtplib和email兩個(gè)模塊嚣崭,email負(fù)責(zé)構(gòu)造郵件笨触,smtplib負(fù)責(zé)發(fā)送郵件。
簡(jiǎn)單郵件傳輸協(xié)議(Simple Mail Transfer Protocol, SMTP)是從源地址到目的地址傳送郵件的規(guī)則雹舀,由該協(xié)議控制信件的中轉(zhuǎn)方式芦劣。
Python的smtplib提供了一種很方便的途徑發(fā)送電子郵件,對(duì)SMTP協(xié)議進(jìn)行了簡(jiǎn)單的封裝说榆。
Python創(chuàng)建SMTP對(duì)象的語(yǔ)法如下:

smtpObj = smtplib.SMTP([host [, port [, local_hostname]]])

語(yǔ)法中各個(gè)參數(shù)說(shuō)明如下虚吟。

  • host: SMTP服務(wù)器主機(jī)∏┎疲可以指定主機(jī)的IP地址或域名(如www.baidu.com)串慰,是可選參數(shù)。
  • port:如果提供了host參數(shù)唱蒸,就需要指定SMTP服務(wù)使用的端口號(hào)邦鲫。一般情況下SMTP的端口號(hào)為25。
  • local_hostname:如果SMTP在本地主機(jī)上神汹,只需要指定服務(wù)器地址為localhost即可庆捺。

如果在創(chuàng)建SMTP對(duì)象時(shí)提供了host和port兩個(gè)參數(shù),在初始化時(shí)會(huì)自動(dòng)調(diào)用connect方法連接服務(wù)器屁魏。
Python SMTP對(duì)象使用sendmail方法發(fā)送郵件的語(yǔ)法如下:

SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options]

語(yǔ)法中各個(gè)參數(shù)說(shuō)明如下滔以。

  • from_addr:郵件發(fā)送者的地址。
  • to_addrs:字符串列表蚁堤,郵件發(fā)送地址醉者。
  • msg:發(fā)送消息。

msg是字符串披诗,表示郵件內(nèi)容撬即。我們知道郵件一般由標(biāo)題、發(fā)信人呈队、收件人剥槐、郵件內(nèi)容、附件等構(gòu)成宪摧,發(fā)送郵件時(shí)粒竖,要注意msg的格式。這個(gè)格式就是SMTP協(xié)議中定義的格式几于。

SMTP類(lèi)中提供了表1所示的一些常用方法蕊苗。
1.SMTP類(lèi)的常用方法

普通文本郵件發(fā)送的實(shí)現(xiàn)關(guān)鍵要將MIMEText中的_subtype設(shè)置為plain。首先導(dǎo)入smtplib和mimetext沿彭。創(chuàng)建smtplib.smtp實(shí)例朽砰,連接郵件SMTP服務(wù)器,登錄后發(fā)送,具體代碼如下:
#! /usr/bin/python
# -*-coding:UTF-8-*-

import smtplib
from email.mime.text import MIMEText
from email.header import Header
sender = 'from@163.com'
pwd = 'xxxxx' #開(kāi)通郵箱服務(wù)后瞧柔,設(shè)置的客戶(hù)端授權(quán)密碼
receivers = ['to@aliyun.com'] # 接收郵件漆弄,可設(shè)置為你的郵箱

# 三個(gè)參數(shù):第一個(gè)為文本內(nèi)容,第二個(gè) plain 設(shè)置文本格式造锅,第三個(gè) utf-8 設(shè)置編碼
message = MIMEText('Python 郵件發(fā)送測(cè)試...', 'plain', 'utf-8')
message['From'] = Header("郵件測(cè)試", 'utf-8')
message['To'] = Header("測(cè)試", 'utf-8')

subject = 'Python SMTP 郵件測(cè)試'
message['Subject'] = Header(subject, 'utf-8')

try:
    # 使用非本地服務(wù)器撼唾,需要建立ssl連接
    smtpObj = smtplib.SMTP_SSL("smtp.163.com", 465)
    smtpObj.login(sender, pwd)
    smtpObj.sendmail(sender, receivers, message.as_string())
    print ("郵件發(fā)送成功")
except smtplib.SMTPException as e:
    print ("Error: 無(wú)法發(fā)送郵件.Case:%s" % e)

我們使用3個(gè)引號(hào)設(shè)置郵件信息。標(biāo)準(zhǔn)郵件需要3個(gè)頭部信息:From哥蔚、To和Subject倒谷。每個(gè)信息直接使用空行分割。
我們通過(guò)實(shí)例化smtplib模塊的SMTP_SSL對(duì)象smtpObj連接SMTP訪問(wèn)糙箍,并使用sendmail方法發(fā)送信息恨锚。
執(zhí)行以上程序,如果你開(kāi)通了非本地郵件服務(wù)倍靡,就會(huì)輸出:

郵件發(fā)送成功

如果本地主機(jī)安裝了sendmail服務(wù),發(fā)送郵件的代碼可以更改為:

sender = 'from@163.com'
receivers = ['to@aliyun.com'] # 接收郵件课舍,可設(shè)置為你的郵箱

# 三個(gè)參數(shù):第一個(gè)為文本內(nèi)容塌西,第二個(gè) plain 設(shè)置文本格式,第三個(gè) utf-8 設(shè)置編碼
message = MIMEText('Python 郵件發(fā)送測(cè)試...', 'plain', 'utf-8')
message['From'] = Header("郵件測(cè)試", 'utf-8')
message['To'] = Header("測(cè)試", 'utf-8')

subject = 'Python SMTP 郵件測(cè)試'
message['Subject'] = Header(subject, 'utf-8')

try:
    smtpObj = smtplib.SMTP("localhost")
    smtpObj.sendmail(sender, receivers, message.as_string())
    print ("郵件發(fā)送成功")
except smtplib.SMTPException as e:
    print ("Error: 無(wú)法發(fā)送郵件.Case:%s" % e)

不需要客戶(hù)端授權(quán)密碼筝尾、SSL連接和登錄服務(wù)捡需。

根據(jù)示例替換相應(yīng)的授權(quán)密碼、SSL連接和登錄服務(wù)后筹淫,本地執(zhí)行效果如圖2所示站辉。
1 普通郵件發(fā)送效果

2 發(fā)送HTML格式的郵件

如果我們要發(fā)送的是HTML郵件,而不是普通的純文本文件怎么辦呢损姜?方法很簡(jiǎn)單饰剥,在構(gòu)造MIMEText對(duì)象時(shí)把HTML字符串傳進(jìn)去,再把第二個(gè)參數(shù)由plain變?yōu)閔tml就可以了摧阅。代碼實(shí)現(xiàn)如下:

#! /usr/bin/python
# -*-coding:UTF-8-*-

import smtplib
from email.mime.text import MIMEText
from email.header import Header

sender = 'from@163.com'
pwd = 'xxxxx' #開(kāi)通郵箱服務(wù)后汰蓉,設(shè)置的客戶(hù)端授權(quán)密碼
receivers = ['to@aliyun.com'] # 接收郵件,可設(shè)置為你的郵箱

mail_msg = """
<p>Python 郵件發(fā)送測(cè)試...</p>
<p><a >這是一個(gè)鏈接</a></p>
"""
message = MIMEText(mail_msg, 'html', 'utf-8')
message['From'] = Header("郵件測(cè)試", 'utf-8')
message['To'] = Header("測(cè)試", 'utf-8')
subject = 'Python SMTP 郵件測(cè)試'
message['Subject'] = Header(subject, 'utf-8')

try:
    # 使用非本地服務(wù)器棒卷,需要建立ssl連接
    smtpObj = smtplib.SMTP_SSL("smtp.163.com", 465)
    smtpObj.login(sender, pwd)
    smtpObj.sendmail(sender, receivers, message.as_string())
    print ("郵件發(fā)送成功")
except smtplib.SMTPException as e:
    print ("Error: 無(wú)法發(fā)送郵件.Case:%s" % e)

執(zhí)行以上程序顾孽,如果你開(kāi)通了非本地郵件服務(wù),就會(huì)輸出:

郵件發(fā)送成功

如果本地主機(jī)安裝了sendmail服務(wù)比规,就不需要客戶(hù)端授權(quán)密碼若厚、SSL連接和登錄服務(wù),直接使用smtplib模塊的SMTP對(duì)象連接本地訪問(wèn)即可蜒什。

3 發(fā)送帶附件的郵件

如果Email中要添加附件怎么辦测秸?
帶附件的郵件可以看作包含文本和各個(gè)附件,可以構(gòu)造一個(gè)MIMEMultipart對(duì)象代表郵件本身,然后往里面添加一個(gè)MIMEText作為郵件正文乞封,再添加表示附件的MIMEBase對(duì)象即可做裙。代碼實(shí)現(xiàn)如下:

#! /usr/bin/python
# -*-coding:UTF-8-*-

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header

sender = 'from@163.com'
pwd = 'xxxxx' #開(kāi)通郵箱服務(wù)后,設(shè)置的客戶(hù)端授權(quán)密碼
receivers = ['to@aliyun.com'] # 接收郵件肃晚,可設(shè)置為你的郵箱

#創(chuàng)建一個(gè)帶附件的實(shí)例
message = MIMEMultipart()
message['From'] = Header("郵件測(cè)試", 'utf-8')
message['To'] = Header("測(cè)試", 'utf-8')
subject = 'Python SMTP 郵件測(cè)試'
message['Subject'] = Header(subject, 'utf-8')

#郵件正文內(nèi)容
message.attach(MIMEText('這是Python 郵件發(fā)送測(cè)試……', 'plain', 'utf-8'))

# 構(gòu)造附件1锚贱,傳送當(dāng)前目錄下的 test.txt 文件
att1 = MIMEText(open('test.txt', 'rb').read(), 'base64', 'utf-8')
att1["Content-Type"] = 'application/octet-stream'
# 這里的filename 可以任意寫(xiě),寫(xiě)什么名字关串,郵件中就顯示什么名字
att1["Content-Disposition"] = 'attachment; filename="test.txt"'
message.attach(att1)

# 構(gòu)造附件2拧廊,傳送當(dāng)前目錄下的 runoob.txt 文件
att2 = MIMEText(open('runoob.txt', 'rb').read(), 'base64', 'utf-8')
att2["Content-Type"] = 'application/octet-stream'
att2["Content-Disposition"] = 'attachment; filename="runoob.txt"'
message.attach(att2)

try:
    # 使用非本地服務(wù)器,需要建立ssl連接
    smtpObj = smtplib.SMTP_SSL("smtp.163.com", 465)
    smtpObj.login(sender, pwd)
    smtpObj.sendmail(sender, receivers, message.as_string())
    print ("郵件發(fā)送成功")
except smtplib.SMTPException as e:
    print ("Error: 無(wú)法發(fā)送郵件.Case:%s" % e)

執(zhí)行以上程序晋修,如果你開(kāi)通了非本地郵件服務(wù)吧碾,就會(huì)輸出:
郵件發(fā)送成功
如果本地主機(jī)安裝了sendmail服務(wù),就不需要客戶(hù)端授權(quán)密碼墓卦、SSL連接和登錄服務(wù)倦春,直接使用smtplib模塊的SMTP對(duì)象連接本地訪問(wèn)即可。

4 發(fā)送圖片

如果要把一個(gè)圖片嵌入郵件正文落剪,怎么做呢睁本?是否可以直接在HTML郵件中鏈接圖片地址?
大部分郵件服務(wù)商都會(huì)自動(dòng)屏蔽帶有外鏈的圖片忠怖,因?yàn)椴恢肋@些鏈接是否指向惡意網(wǎng)站呢堰。要把圖片嵌入郵件正文,我們只需按照發(fā)送附件的方式把郵件作為附件添加進(jìn)去凡泣,然后在HTML中通過(guò)引用src="cid:0"把附件作為圖片嵌入枉疼。如果有多張圖片,就需要給它們依次編號(hào)鞋拟,然后引用不同的cid:x骂维。

#! /usr/bin/python
# -*-coding:UTF-8-*-

import smtplib
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header

sender = 'from@163.com'
pwd = 'xxxxx' #開(kāi)通郵箱服務(wù)后,設(shè)置的客戶(hù)端授權(quán)密碼
receivers = ['to@aliyun.com'] # 接收郵件贺纲,可設(shè)置為你的郵箱

msgRoot = MIMEMultipart('related')
msgRoot['From'] = Header("郵件測(cè)試", 'utf-8')
msgRoot['To'] =  Header("測(cè)試", 'utf-8')
subject = 'Python SMTP 郵件測(cè)試'
msgRoot['Subject'] = Header(subject, 'utf-8')

msgAlternative = MIMEMultipart('alternative')
msgRoot.attach(msgAlternative)
mail_msg = """
<p>Python 郵件發(fā)送測(cè)試...</p>
<p><a >Python 官方網(wǎng)站</a></p>
<p>圖片演示:</p>
<p><img src="cid:image1"></p>
"""
msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8'))

# 指定圖片為當(dāng)前目錄
fp = open('test.png', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()

# 定義圖片 ID席舍,在 HTML 文本中引用
msgImage.add_header('Content-ID', '<image1>')
msgRoot.attach(msgImage)

try:
    # 使用非本地服務(wù)器,需要建立ssl連接
    smtpObj = smtplib.SMTP_SSL("smtp.163.com", 465)
    smtpObj.login(sender, pwd)
    smtpObj.sendmail(sender, receivers, msgRoot.as_string())
    print ("郵件發(fā)送成功")
except smtplib.SMTPException as e:
    print ("Error: 無(wú)法發(fā)送郵件.Case:%s" % e)

執(zhí)行以上程序哮笆,如果你開(kāi)通了非本地郵件服務(wù)来颤,就會(huì)輸出:

郵件發(fā)送成功

如果本地主機(jī)安裝了sendmail服務(wù),就不需要客戶(hù)端授權(quán)密碼稠肘、SSL連接和登錄服務(wù)福铅,直接使用smtplib模塊的SMTP對(duì)象連接本地訪問(wèn)即可。

5 同時(shí)支持HTML和Plain格式

如果我們發(fā)送HTML郵件项阴,收件人通過(guò)瀏覽器或Outlook之類(lèi)的軟件就可以正常瀏覽郵件內(nèi)容滑黔。如果收件人使用的設(shè)備太古老笆包,查看不了HTML郵件怎么辦呢?
辦法是在發(fā)送HTML的同時(shí)附加一個(gè)純文本略荡,如果收件人無(wú)法查看HTML格式的郵件庵佣,就可以自動(dòng)降級(jí)查看純文本郵件。
利用MIMEMultipart可以組合一個(gè)HTML和Plain汛兜,注意指定subtype是alternative巴粪。使用代碼格式如下:

#! /usr/bin/python
# -*-coding:UTF-8-*-

import smtplib
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header

sender = '15618942985@163.com'
pwd = 'lyz111LYZ' #開(kāi)通郵箱服務(wù)后,設(shè)置的客戶(hù)端授權(quán)密碼
receivers = ['yuzhouliu@aliyun.com'] # 接收郵件粥谬,可設(shè)置為你的郵箱

msgRoot = MIMEMultipart('related')
msgRoot['From'] = Header("郵件測(cè)試", 'utf-8')
msgRoot['To'] =  Header("測(cè)試", 'utf-8')
subject = 'Python SMTP 郵件測(cè)試'
msgRoot['Subject'] = Header(subject, 'utf-8')

msgAlternative = MIMEMultipart('alternative')
msgRoot.attach(msgAlternative)

msgAlternative.attach(MIMEText('hello', 'plain', 'utf-8'))
mail_msg = '<html><body><h1>Hello</h1></body></html>'
msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8'))

# 指定圖片為當(dāng)前目錄
fp = open('test.png', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()

# 定義圖片 ID肛根,在 HTML 文本中引用
msgImage.add_header('Content-ID', '<image1>')
msgRoot.attach(msgImage)

try:
    # 使用非本地服務(wù)器,需要建立ssl連接
    smtpObj = smtplib.SMTP_SSL("smtp.163.com", 465)
    smtpObj.login(sender, pwd)
    smtpObj.sendmail(sender, receivers, msgRoot.as_string())
    print ("郵件發(fā)送成功")
except smtplib.SMTPException as e:
    print ("Error: 無(wú)法發(fā)送郵件.Case:%s" % e)

執(zhí)行以上程序漏策,如果你開(kāi)通了非本地郵件服務(wù)派哲,就會(huì)輸出:

郵件發(fā)送成功

查看收到的郵件,如圖2所示掺喻。


2 接收帶附件的郵件

6 加密SMTP

使用標(biāo)準(zhǔn)25端口連接SMTP服務(wù)器時(shí)使用的是明文傳輸芭届,發(fā)送郵件的整個(gè)過(guò)程可能會(huì)被竊聽(tīng)。要更安全地發(fā)送郵件感耙,可以加密SMTP會(huì)話喉脖,實(shí)際上是先創(chuàng)建SSL安全連接,然后使用SMTP協(xié)議發(fā)送郵件抑月。
某些郵件服務(wù)商(如Gmail)提供的SMTP服務(wù)必須進(jìn)行加密傳輸。下面來(lái)看如何通過(guò)Gmail提供的安全SMTP發(fā)送郵件舆蝴。
由于Gmail的SMTP端口是587谦絮,因此修改代碼如下:

smtp_server = 'smtp.gmail.com'
smtp_port = 587
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
# 剩下的代碼和前面的一模一樣:
server.set_debuglevel(1)
...

只需要在創(chuàng)建SMTP對(duì)象后立刻調(diào)用starttls()方法,就可以創(chuàng)建安全連接洁仗。后面的代碼和前面的發(fā)送郵件代碼完全一樣层皱。
如果因?yàn)榫W(wǎng)絡(luò)問(wèn)題無(wú)法連接Gmail的SMTP服務(wù)器,請(qǐng)相信我們的代碼是沒(méi)有問(wèn)題的赠潦,需要對(duì)網(wǎng)絡(luò)設(shè)置做必要的調(diào)整叫胖。

POP3接收郵件

SMTP用于發(fā)送郵件,如果要收取郵件呢她奥?
收取郵件就是編寫(xiě)一個(gè)MUA作為客戶(hù)端瓮增,從MDA獲取郵件到用戶(hù)的電腦或手機(jī)上。收取郵件最常用的協(xié)議是POP哩俭,目前版本是3绷跑,俗稱(chēng)POP3。
Python內(nèi)置了一個(gè)poplib模塊凡资,用于實(shí)現(xiàn)POP3協(xié)議砸捏,可以直接用來(lái)收取郵件。
注意POP3協(xié)議收取的不是可以閱讀的郵件,而是郵件的原始文本垦藏。這和SMTP協(xié)議很像梆暖,SMTP發(fā)送的也是經(jīng)過(guò)編碼后的一大段文本。
要把POP3收取的文本變成可以閱讀的郵件掂骏,還需要用email模塊提供的各種類(lèi)解析原始文本轰驳。
收取郵件分為以下兩個(gè)步驟:
(1)用poplib把郵件的原始文本下載到本地。
(2)用email解析原始文本芭挽,還原為郵件對(duì)象滑废。

1 POP3下載郵件

POP3協(xié)議很簡(jiǎn)單。下面獲取最新一封郵件的內(nèi)容袜爪,代碼如下:

#! /usr/bin/python
# -*-coding:UTF-8-*-

import poplib
from email.parser import Parser

# 輸入郵件地址蠕趁、口令和POP3服務(wù)器地址
email = input('Email: ')
password = input('Password: ')
pop3_server = input('POP3 server: ')

# 連接到POP3 服務(wù)器
server = poplib.POP3(pop3_server)
# 可以打開(kāi)或關(guān)閉調(diào)試信息
server.set_debuglevel(1)
# 可選:輸出POP3服務(wù)器的歡迎文字
print(server.getwelcome().decode('utf-8'))

# 身份認(rèn)證
server.user(email)
server.pass_(password)
# stat()返回郵件數(shù)量和占用空間
print('Messages: %s. Size: %s' % server.stat())
# list()返回所有郵件的編號(hào)
resp, mails, octets = server.list()
# 可以查看返回的列表,類(lèi)似[b'1 82923', b'2 2184', ...]
print(mails)

# 獲取最新一封郵件, 注意索引號(hào)從1 開(kāi)始
index = len(mails)
resp, lines, octets = server.retr(index)

# lines 存儲(chǔ)了郵件原始文本的每一行
# 可以獲得整個(gè)郵件的原始文本
msg_content = b'\r\n'.join(lines).decode('utf-8')
# 稍后解析郵件
msg = Parser().parsestr(msg_content)

# 可以根據(jù)郵件索引號(hào)直接從服務(wù)器刪除郵件
# server.dele(index)
# 關(guān)閉連接
server.quit()

用POP3獲取郵件其實(shí)很簡(jiǎn)單辛馆,要獲取所有郵件俺陋,只需要循環(huán)使用retr()把每一封郵件的內(nèi)容拿到即可。真正麻煩的是把郵件的原始內(nèi)容解析為可以閱讀的郵件對(duì)象昙篙。

2 解析郵件

解析郵件的過(guò)程和構(gòu)造郵件正好相反腊状,需要先導(dǎo)入必要的模塊:

from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr

import poplib

只需要一行代碼就可以把郵件內(nèi)容解析為Message對(duì)象:

msg = Parser().parsestr(msg_content)

這個(gè)Message對(duì)象可能是一個(gè)MIMEMultipart對(duì)象,即包含嵌套的其他MIMEBase對(duì)象苔可,嵌套可能還不止一層缴挖。
我們要遞歸地輸出Message對(duì)象的層次結(jié)構(gòu):

# indent 用于縮進(jìn)顯示:
def print_info(msg, indent=0):
    if indent == 0:
         for header in ['From', 'To', 'Subject']:
             value = msg.get(header, '')
             if value:
                  if header=='Subject':
                      value = decode_str(value)
                  else:
                      hdr, addr = parseaddr(value)
                      name = decode_str(hdr)
                      value = u'%s <%s>' % (name, addr)
             print('%s%s: %s' % (' ' * indent, header, value))
    if (msg.is_multipart()):
         parts = msg.get_payload()
         for n, part in enumerate(parts):
             print('%spart %s' % (' ' * indent, n))
             print('%s--------------------' % (' ' * indent))
             print_info(part, indent + 1)
    else:
         content_type = msg.get_content_type()
         if content_type=='text/plain' or content_type=='text/html':
             content = msg.get_payload(decode=True)
             charset = guess_charset(msg)
             if charset:
                  content = content.decode(charset)
             print('%sText: %s' % (' ' * indent, content + '...'))
         else:
             print('%sAttachment: %s' % (' ' * indent, content_type))

郵件的Subject或Email中包含的名字都是經(jīng)過(guò)編碼的str,要正常顯示必須進(jìn)行解碼焚辅,代碼如下:

def decode_str(s):
    value, charset = decode_header(s)[0]
    if charset:
         value = value.decode(charset)
    return value

decode_header()返回一個(gè)list映屋,因?yàn)橄馛c、Bcc這樣的字段可能包含多個(gè)郵件地址同蜻,所以會(huì)解析出多個(gè)元素棚点。編寫(xiě)上面的代碼時(shí)偷懶了,只取了第一個(gè)元素湾蔓。
文本郵件的內(nèi)容也是str瘫析,還需要檢測(cè)編碼,否則非UTF-8編碼的郵件都無(wú)法正常顯示默责,代碼如下:

def guess_charset(msg):
    charset = msg.get_charset()
    if charset is None:
         content_type = msg.get('Content-Type', '').lower()
         pos = content_type.find('charset=')
         if pos >= 0:
             charset = content_type[pos + 8:].strip()
    return charset
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贬循,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子桃序,更是在濱河造成了極大的恐慌甘有,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葡缰,死亡現(xiàn)場(chǎng)離奇詭異亏掀,居然都是意外死亡忱反,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)滤愕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)温算,“玉大人,你說(shuō)我怎么就攤上這事间影∽⒏停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵魂贬,是天一觀的道長(zhǎng)巩割。 經(jīng)常有香客問(wèn)我,道長(zhǎng)付燥,這世上最難降的妖魔是什么宣谈? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮键科,結(jié)果婚禮上闻丑,老公的妹妹穿的比我還像新娘。我一直安慰自己勋颖,他們只是感情好嗦嗡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著饭玲,像睡著了一般侥祭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茄厘,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天矮冬,我揣著相機(jī)與錄音,去河邊找鬼蚕断。 笑死,一個(gè)胖子當(dāng)著我的面吹牛入挣,可吹牛的內(nèi)容都是我干的亿乳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼径筏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼葛假!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起滋恬,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤聊训,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后恢氯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體带斑,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鼓寺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勋磕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妈候。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挂滓,靈堂內(nèi)的尸體忽然破棺而出苦银,到底是詐尸還是另有隱情,我是刑警寧澤赶站,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布幔虏,位于F島的核電站,受9級(jí)特大地震影響贝椿,放射性物質(zhì)發(fā)生泄漏想括。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一团秽、第九天 我趴在偏房一處隱蔽的房頂上張望主胧。 院中可真熱鬧,春花似錦习勤、人聲如沸踪栋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)夷都。三九已至,卻和暖如春予颤,著一層夾襖步出監(jiān)牢的瞬間囤官,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蛤虐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留党饮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓驳庭,卻偏偏與公主長(zhǎng)得像刑顺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子饲常,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容