郵件是我們?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.com、xxx@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é)議中定義的格式几于。
普通文本郵件發(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ù)捡需。
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所示掺喻。
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