您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Python如何使用email、smtplib、poplib、imaplib模塊收發郵件”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Python如何使用email、smtplib、poplib、imaplib模塊收發郵件”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
一封電子郵件的旅程是:
MUA:Mail User Agent——郵件用戶代理。(即類似Outlook的電子郵件軟件)
MTA:Mail Transfer Agent——郵件傳輸代理,就是那些Email服務提供商,比如網易、新浪等等。
MDA:Mail Delivery Agent——郵件投遞代理。Email服務提供商的某個服務器
發件人 -> MUA -> MTA -> MTA -> 若干個MTA -> MDA <- MUA <- 收件人
要編寫程序來發送和接收郵件,本質上就是:
編寫MUA把郵件發到MTA;
編寫MUA從MDA上收郵件。
發郵件時,MUA和MTA使用的協議就是SMTP:Simple Mail Transfer Protocol,后面的MTA到另一個MTA也是用SMTP協議。
收郵件時,MUA和MDA使用的協議有兩種:POP:Post Office Protocol,目前版本是3,俗稱POP3;IMAP:Internet Message Access Protocol,目前版本是4,優點是不但能取郵件,還可以直接操作MDA上存儲的郵件,比如從收件箱移到垃圾箱,等等。
即簡單郵件傳輸協議,它是一組用于由源地址到目的地址傳送郵件的規則,由它來控制信件的中轉方式。
python的smtplib提供了一種很方便的途徑發送電子郵件。它對smtp協議進行了簡單的封裝。
Python對SMTP支持有smtplib
和email
兩個模塊,email
負責構造郵件,smtplib
負責發送郵件。
構造一個郵件對象就是一個Messag
對象,如果構造一個MIMEText
對象,就表示一個文本郵件對象,如果構造一個MIMEImage
對象,就表示一個作為附件的圖片,要把多個對象組合起來,就用MIMEMultipart
對象,而MIMEBase
可以表示任何對象。它們的繼承關系如下:
Message +- MIMEBase +- MIMEMultipart +- MIMENonMultipart +- MIMEMessage +- MIMEText +- MIMEImage
首先,我們來構造一個最簡單的純文本郵件,然后,通過SMTP發出去。
from email.mime.text import MIMEText msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
注意到構造MIMEText
對象時,第一個參數就是郵件正文,第二個參數是MIME的subtype,傳入'plain'
,最終的MIME就是'text/plain'
,最后一定要用utf-8
編碼保證多語言兼容性。
語法如下:
import smtplib smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )
參數說明:
host: SMTP 服務器主機。 你可以指定主機的ip地址或者域名如: runoob.com,這個是可選參數。
port: 如果你提供了 host 參數, 你需要指定 SMTP 服務使用的端口號,一般情況下 SMTP 端口號為25。
local_hostname: 如果 SMTP 在你的本機上,你只需要指定服務器地址為 localhost 即可。
語法如下:
SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options])
參數說明:
from_addr: 郵件發送者地址。
to_addrs: 字符串列表,郵件發送地址。
msg: 發送消息
這里要注意一下第三個參數,msg 是字符串,表示郵件。我們知道郵件一般由標題,發信人,收件人,郵件內容,附件等構成,發送郵件的時候,要注意 msg 的格式。這個格式就是 smtp 協議中定義的格式。
以下執行實例需要你本機已安裝了支持 SMTP 的服務。
sendmail()
方法就是發郵件,由于可以一次發給多個人,所以傳入一個list
,郵件正文是一個str
,as_string()
把MIMEText
對象變成str
。
經過Header
對象編碼的文本,包含utf-8編碼信息和Base64編碼的文本。
以下是一個使用 Python 發送郵件簡單的實例:
import smtplib from email.mime.text import MIMEText from email.header import Header sender = 'from@runoob.com' receivers = ['429240967@qq.com'] # 接收郵件,可設置為你的QQ郵箱或者其他郵箱 # 三個參數:第一個為文本內容,第二個 plain 設置文本格式,第三個 utf-8 設置編碼 message = MIMEText('Python 郵件發送測試內容...', 'plain', 'utf-8') message['From'] = Header("菜鳥教程", 'utf-8') # 發送者 message['To'] = Header("測試", 'utf-8') # 接收者 message['Subject'] = Header('Python SMTP 郵件測試主題', 'utf-8') try: smtpObj = smtplib.SMTP('localhost') smtpObj.sendmail(sender, receivers, message.as_string()) print "郵件發送成功" except smtplib.SMTPException: print "Error: 無法發送郵件"
如果我們本機沒有 sendmail 訪問,也可以使用其他郵件服務商的 SMTP 訪問(QQ、網易、Google等)。
login()
方法用來登錄SMTP服務器
發收件件人的名字沒有顯示為友好的名字,比如Mr Green
;
使用 formataddr方法來格式化一個郵件地址。如果包含中文,需要通過Header
對象進行編碼。
msg['To']
接收的是字符串而不是list,如果有多個郵件地址,用,
分隔即可。
import smtplib from email.mime.text import MIMEText from email.utils import formataddr # 第三方 SMTP 服務 mail_host = "mail.sss.com" # 設置服務器 mail_user = "it_system@sss.com" # 用戶名 mail_pass = "Ssss201709#" # 口令 sender = 'it_system@tcl.com' receivers = 'sss.yang@tcsssl.com' # 接收郵件,可設置為你的QQ郵箱或者其他郵箱 message = MIMEText('Python 郵件內容測試...', 'plain', 'utf-8') message['From'] = formataddr(('SCBC-啊iT', sender)) message['To'] = formataddr(('楊生', receivers)) message['Subject'] = 'Python SMTP 郵件測試' try: smtpObj = smtplib.SMTP() smtpObj.connect(mail_host, 25) # 25 為 SMTP 端口號 smtpObj.login(mail_user, mail_pass) smtpObj.sendmail(sender, receivers, message.as_string()) print("郵件發送成功") except smtplib.SMTPException: print("Error: 無法發送郵件")
Python在構造MIMEText
對象時,把HTML字符串傳進去,再把第二個參數由plain
變為html
就可以了:
具體代碼如下:
mail_msg = """ Python 郵件內容測試... 這是一個鏈接 """ message = MIMEText(mail_msg, 'html', 'utf-8')
帶附件的郵件可以看做包含若干部分的郵件:文本和各個附件本身,所以,可以構造一個MIMEMultipart
對象代表郵件本身,然后往里面加上一個MIMEText
作為郵件正文,再繼續往里面加上表示附件的MIMEBase
對象即可。
發送帶附件的郵件,首先要創建MIMEMultipart()實例,然后構造附件,如果有多個附件,可依次構造,最后利用smtplib.smtp發送。
from email.mime.multipart import MIMEMultipart # 創建一個帶附件的實例 message = MIMEMultipart() message['From'] = formataddr(('SCBC-啊iT', sender)) message['To'] = formataddr(('楊生', receivers)) message['Subject'] = 'Python SMTP 郵件測試' mail_msg = """ Python 郵件內容測試... 這是一個鏈接 """ # 郵件正文內容 message.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 構造附件1,傳送當前目錄下的 test.txt 文件 att = MIMEText(open('32.txt', 'rb').read(), 'base64', 'utf-8') att["Content-Type"] = 'application/octet-stream' # 這里的filename可以任意寫,寫什么名字,郵件中顯示什么名字 att["Content-Disposition"] = 'attachment; filename="32.txt"' message.attach(att)
要把圖片嵌入到郵件正文中,我們只需按照發送附件的方式,先把郵件作為附件添加進去,然后,在HTML中通過引用src="cid:0"
就可以把附件作為圖片嵌入了。如果有多個圖片,給它們依次編號,然后引用不同的cid:x
即可。
郵件的 HTML 文本中一般郵件服務商添加外鏈是無效的,正確添加圖片的實例如下所示:
from email.mime.multipart import MIMEMultipart from email.mime.image import MIMEImage # 創建一個帶附件的實例 message = MIMEMultipart() message['From'] = formataddr(('SCBC-啊iT', sender)) message['To'] = formataddr(('楊生', receivers)) message['Subject'] = 'Python SMTP 郵件測試' mail_msg = """ Python 郵件內容測試... 這是一個鏈接 圖片演示: """ # 郵件正文內容 message.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 指定圖片為當前目錄 with open('a.jpg', 'rb') as fp: msgImage = MIMEImage(fp.read()) # 定義圖片 ID,在 HTML 文本中引用 msgImage.add_header('Content-ID', '') message.attach(msgImage)
或者通過MIMEBase來添加圖片
# 指定圖片為當前目錄 with open('a.jpg', 'rb') as fp: # 設置附件的MIME和文件名,這里是png類型: mime = MIMEBase('image', 'jpg', filename='a.jpg') # 加上必要的頭信息: mime.add_header('Content-Disposition', 'attachment', filename='附件顯示名稱.jpg') mime.add_header('Content-ID', '') # 如果有多個文件需要使用.format(index) mime.add_header('X-Attachment-Id', '0') # 如果有多個文件需要使用.format(index) # 把附件的內容讀進來: mime.set_payload(fp.read()) # 用Base64編碼: encoders.encode_base64(mime) # 添加到MIMEMultipart: message.attach(mime)
如果我們發送HTML郵件,收件人通過瀏覽器或者Outlook之類的軟件是可以正常瀏覽郵件內容的,但是,如果收件人使用的設備太古老,查看不了HTML郵件怎么辦?
辦法是在發送HTML的同時再附加一個純文本,如果收件人無法查看HTML格式的郵件,就可以自動降級查看純文本郵件。
利用MIMEMultipart
就可以組合一個HTML和Plain,要注意指定subtype是alternative
:
msg = MIMEMultipart('alternative') msg['From'] = ... msg['To'] = ... msg['Subject'] = ... msg.attach(MIMEText('hello', 'plain', 'utf-8')) msg.attach(MIMEText('Hello', 'html', 'utf-8')) # 正常發送msg對象...
使用標準的25端口連接SMTP服務器時,使用的是明文傳輸,發送郵件的整個過程可能會被竊聽。要更安全地發送郵件,可以加密SMTP會話,實際上就是先創建SSL安全連接,然后再使用SMTP協議發送郵件。
某些郵件服務商,例如Gmail,提供的SMTP服務必須要加密傳輸。我們來看看如何通過Gmail提供的安全SMTP發送郵件。
只需要在創建SMTP
對象后,立刻調用starttls()
方法,就創建了安全連接。后面的代碼和前面的發送郵件代碼完全一樣。
必須知道,Gmail的SMTP端口是587,因此,修改代碼如下:
smtp_server = 'smtp.gmail.com' smtp_port = 587 server = smtplib.SMTP(smtp_server, smtp_port) server.starttls() # 剩下的代碼和前面的一模一樣: server.set_debuglevel(1)
收取郵件就是編寫一個MUA作為客戶端,從MDA把郵件獲取到用戶的電腦或者手機上。收取郵件最常用的協議是POP協議,目前版本號是3,俗稱POP3。
Python內置一個poplib
模塊,實現了POP3協議,可以直接用來收郵件。
POP3 的命令和響應數據都是基于 ASCII 文本的,并以 CR 和 LF(/r/n) 作為行結束符,響應數據包括一個表示返回狀態的符號(+/)和描述信息。
請求和響應的標準格式如下:
請求標準格式:命令 [參數] CRLF
響應標準格式:+OK /[-ERR] description CRLF
POP3 協議客戶端的命令和服務器端對應的響應數據如下:
user name:向 POP 服務器發送登錄的用戶名。
pass string:向 POP 服務器發送登錄的密碼。
quit:退出 POP 服務器。
stat:統計郵件服務器狀態,包括郵件數和總大小。
list [msg_no]:列出全部郵件或指定郵件。返回郵件編號和對應大小。
retr msg_no:獲取指定郵件的內容(根據郵件編號來獲取,編號從 1 開始)。
dele msg_no:刪除指定郵件(根據郵件編號來刪除,編號從 1 開始)。
noop:空操作。僅用于與服務器保持連接。
rset:用于撤銷 dele 命令。
poplib 模塊完全模擬了上面命令,poplib.POP3 或 poplib.POP3_SSL 為上面命令提供了相應的方法,開發者只要依次使用上面命令即可從服務器端下載對應的郵件
注意到POP3協議收取的不是一個已經可以閱讀的郵件本身,而是郵件的原始文本,這和SMTP協議很像,SMTP發送的也是經過編碼后的一大段文本。
要把POP3收取的文本變成可以閱讀的郵件,還需要用email
模塊提供的各種類來解析原始文本,變成可閱讀的郵件對象。
所以,收取郵件分兩步:
用POP3獲取郵件其實很簡單,要獲取所有郵件,只需要循環使用retr()
把每一封郵件內容拿到即可。真正麻煩的是把郵件的原始內容解析為可以閱讀的郵件對象。
import poplib from email.parser import Parser # email.parser 解析電子郵件,返回這個對象的email.message.Message實例 from email.header import decode_header from email.utils import parseaddr # 服務器及用戶信息 host = 'mail.tcl.com' username = 'bobin.yang@tcl.com' password = 'Ybb7654321' # 連接到POP3服務器 conn = poplib.POP3_SSL(host) # 注意qq郵箱使用SSL連接 # 設置調試模式,可以看到與服務器的交互信息 conn.set_debuglevel(1) # 打印POP3服務器的歡迎文字 print(conn.getwelcome().decode("utf-8")) # 身份認證 conn.user(username) conn.pass_(password) # 獲取服務器上信件信息,返回一個列表,第一項是一共有多少封郵件,第二項是共有多少字節 # stat()返回郵件數量和占用空間 mail_total, total_size = conn.stat() print('message: %s.Size:%s' % (mail_total, total_size)) # list()返回(response, ['mesg_num octets', ...], octets),第二項是編號 resp, mails, octets = conn.list() print(mails) # 返回的列表類似[b'1 82923', b'2 2184', ...] # 獲取最新一封郵件,注意索引號從1開始 # POP3.retr(which) 檢索序號which的這個郵件,然后設置他的出現標志 返回(response, ['line', ...], octets)這個三元組 resp, lines, ocetes = conn.retr(len(mails)) print('lines:', len(lines)) # lines 存儲了郵件的原始文本的每一行 # 可以獲得整個郵件的原始文本 print("-------------------")
消息
對象,然后,用適當的形式把郵件內容展示給用戶即可。解析郵件的過程和上一節構造郵件正好相反。
程序在創建 BytesParser(解析字節串格式的郵件數據)或 Parser(解析字符串格式的郵件數據)時,必須指定 policy=default
;否則,BytesParse 或 Parser 解析郵件數據得到的就是過時的 Message 對象,,不是新的 EmailMessage,處理起來非常不方便。
msg = b'\r\n'.join(lines).decode('utf-8') # 解析出郵件 msg = Parser().parsestr(msg) # email.Parser.parsestr(text, headersonly=False) # 與parser()方法類似,不同的是他接受一個字符串對象而不是一個類似文件的對象 # 可選的headersonly表示是否在解析玩標題后停止解析,默認為否 # 返回根消息對象 # 編碼處理,文本郵件的內容也是str,還需要檢測編碼,否則,非UTF-8編碼的郵件都無法正常顯示 def guess_charset(msg): charset = msg.get_charset() # 從msg對象獲取編碼 if charset is None: content_type = msg.get('Content-Type', '').lower() # 如果獲取不到,再從content—type字段獲取 if 'charset' in content_type: charset = content_type.split('charset=')[1].strip() return charset return charset # 數據解碼,郵件的Subject或者Email中包含的名字都是經過編碼后的str,要正常顯示,就必須decode def decode_str(s): value, charset = decode_header(s)[0] # 數據,數據編碼方式,from email.header import decode_header # decode_header()返回一個list,因為像Cc、Bcc這樣的字段可能包含多個郵件地址,所以解析出來的會有多個元素。上面的代碼我們偷了個懶,只取了第一個元素。 if charset: value = value.decode(charset) return value # print_ingo函數:解析郵件與構造郵件的步驟正好相反 def print_info(msg, indent=0): # indent用于縮進顯示 if indent == 0: for header in ['From', 'To', 'Subject']: # 郵件的from、to、subject存在于根對象上 value = msg.get(header, '') if value: if header == 'Subject': value = decode_str(value) # 需要解碼subject字符串 else: # 解碼mail地址 hdr, addr = parseaddr(value) name = decode_str(hdr) value = '%s' % addr print('%s: %s %s' % (header, value, name)) print('-*-' * 20) if msg.is_multipart(): # 如果郵件對象是一個is_multipart,get_payload()返回一個list,包含所有子對象 parts = msg.get_payload() # 循環獲得列表項 for n, part in enumerate(parts): # print('%spart %s' % (' ' * indent, n)) # print('%s------------' % (' ' * indent)) # 遞歸打印沒一個子對象 print_info(part, indent + 1) else: # 郵件對象不是一個is_multipart,就根據content_type判斷 content_type = msg.get_content_type() # 數據類型 if content_type == 'text/plain' or content_type == 'text/html': # 純文本 html文本 # 純文本或html內容 content = msg.get_payload(decode=True) # 獲得文本對象的字符串而非對象本身 charset = guess_charset(msg) # 要檢測文本編碼 if charset: content = content.decode(charset) content = '%s' % (content) print(content) # 獲取郵件文本內容,如果只有文本,打印顯示的結果和郵件中看的效果一模一樣 else: print(content_type+'不是文本') print_info(msg, 0) # 退出 conn.quit()
如果程序要獲取郵件的發件人、收件人和主題,直接通過 EmailMessage 的相應屬性來獲取即可,與前面為 EmailMessage 設置發件人、收件人和主題的方式是對應的。
如果程序要讀取 EmailMessage 的各部分,則需要調用該對象的 walk() 方法,該方法返回一個可迭代對象,程序使用 for 循環遍歷 walk() 方法的返回值,對郵件內容進行逐項處理:
如果郵件某項的 maintype 是 'multipart',則說明這一項是容器,用于包含郵件內容、附件等其他項。
如果郵件某項的 maintype 是 'text',則說明這一項的內容是文本,通常就是郵件正文或文本附件。對于這種文本內容,程序直接將其輸出到控制臺中。
如果郵件某項的 maintype 是其他,則說明這一項的內容是附件,程序將附件內容保存在本地文件中。
import os import poplib import mimetypes from email.parser import Parser, BytesParser from email.policy import default msg_data = b'\r\n'.join(lines) # 將字符串內容解析成郵件,此處一定要指定policy=default msg = BytesParser(policy=default).parsebytes(msg_data) print(type(msg)) print('發件人:' + msg['from']) print('收件人:' + msg['to']) print('主題:' + msg['subject']) print('第一個收件人名字:' + msg['to'].addresses[0].username) print('第一個發件人名字:' + msg['from'].addresses[0].username) for part in msg.walk(): counter = 1 # 如果maintype是multipart,說明是容器(用于包含正文、附件等) if part.get_content_maintype() == 'multipart': continue # 如果maintype是multipart,說明是郵件正文部分 elif part.get_content_maintype() == 'text': print(part.get_content()) # 處理附件 else: # 獲取附件的文件名 filename = part.get_filename() # 如果沒有文件名,程序要負責為附件生成文件名 if not filename: # 根據附件的contnet_type來推測它的后綴名 ext = mimetypes.guess_extension(part.get_content_type()) # 如果推測不出后綴名 if not ext: # 使用.bin作為后綴名 ext = '.bin' # 程序為附件來生成文件名 filename = 'part-%03d%s' % (counter, ext) counter += 1 # 將附件寫入的本地文件 with open(os.path.join('.', filename), 'wb') as fp: fp.write(part.get_payload(decode=True)) # 退出服務器,相當于發送POP 3的quit命令 conn.quit()
通過IMAP協議來管理郵箱用的,稱作交互郵件訪問協議。
! encoding:utf8 ''' 環境: Win10 64位 Python 2.7.5 參考: http://www.pythonclub.org/python-network-application/email-format http://blog.sina.com.cn/s/blog_4deeda2501016eyf.html ''' import imaplib import email def parseHeader(message): """ 解析郵件首部 """ subject = message.get('subject') h = email.Header.Header(subject) dh = email.Header.decode_header(h) subject = unicode(dh[0][0], dh[0][1]).encode('gb2312') # 主題 print subject print ' ' # 發件人 print 'From:', email.utils.parseaddr(message.get('from'))[1] print ' ' # 收件人 print 'To:', email.utils.parseaddr(message.get('to'))[1] print ' ' # 抄送人 print 'Cc:',email.utils.parseaddr(message.get_all('cc'))[1] def parseBody(message): """ 解析郵件/信體 """ # 循環信件中的每一個mime的數據塊 for part in message.walk(): # 這里要判斷是否是multipart,是的話,里面的數據是一個message 列表 if not part.is_multipart(): charset = part.get_charset() # print 'charset: ', charset contenttype = part.get_content_type() # print 'content-type', contenttype name = part.get_param("name") #如果是附件,這里就會取出附件的文件名 if name: # 有附件 # 下面的三行代碼只是為了解碼象=?gbk?Q?=CF=E0=C6=AC.rar?=這樣的文件名 fh = email.Header.Header(name) fdh = email.Header.decode_header(fh) fname = dh[0][0] print '附件名:', fname # attach_data = par.get_payload(decode=True) # 解碼出附件數據,然后存儲到文件中 # try: # f = open(fname, 'wb') #注意一定要用wb來打開文件,因為附件一般都是二進制文件 # except: # print '附件名有非法字符,自動換一個' # f = open('aaaa', 'wb') # f.write(attach_data) # f.close() else: #不是附件,是文本內容 print part.get_payload(decode=True) # 解碼出文本內容,直接輸出來就可以了。 # pass # print '+'*60 # 用來區別各個部分的輸出 def getMail(host, username, password, port=993): try: serv = imaplib.IMAP4_SSL(host, port) except Exception, e: serv = imaplib.IMAP4(host, port) serv.login(username, password) serv.select() # 搜索郵件內容 typ, data = serv.search(None, '(FROM "xx@xxx.com")') count = 1 pcount = 1 for num in data[0].split()[::-1]: typ, data = serv.fetch(num, '(RFC822)') text = data[0][1] message = email.message_from_string(text) # 轉換為email.message對象 parseHeader(message) print ' ' parseBody(message) pcount += 1 if pcount > count: break serv.close() serv.logout() if __name__ == '__main__': host = "imap.mail_serv.com" # "pop.mail_serv.com" username = "Trevor@mail_serv.com" password = "your_password" getMail(host, username, password)
讀到這里,這篇“Python如何使用email、smtplib、poplib、imaplib模塊收發郵件”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。