概述
在日常工作生活中,都是利用個人或公司的郵箱客戶端進行收發郵件,那么如何打造一款屬於自己的郵箱客戶端呢?本文以一個簡單的小例子,簡述如何通過Pyhton的imaplib和email兩大模塊,實現郵件的接收並展示,僅供學習分享使用,如有不足之處,還請指正。
什么是IMAP?
IMAP,即Internet Message Access Protocol(互聯網郵件訪問協議),您可以通過這種協議從郵件服務器上獲取郵件的信息、下載郵件等。IMAP與POP類似,都是一種郵件獲取協議。
IMAP和POP有什么區別?
POP允許電子郵件客戶端下載服務器上的郵件,但是您在電子郵件客戶端的操作(如:移動郵件、標記已讀等),這是不會反饋到服務器上的,比如:您通過電子郵件客戶端收取了QQ郵箱中的3封郵件並移動到了其他文件夾,這些移動動作是不會反饋到服務器上的,也就是說,QQ郵箱服務器上的這些郵件是沒有同時被移動的 。但是IMAP就不同了,電子郵件客戶端的操作都會反饋到服務器上,您對郵件進行的操作(如:移動郵件、標記已讀等),服務器上的郵件也會做相應的動作。也就是說,IMAP是“雙向”的。
同時,IMAP可以只下載郵件的主題,只有當您真正需要的時候,才會下載郵件的所有內容。
如何設置IMAP服務的SSL加密方式?
使用SSL的通用配置如下:
接收郵件服務器:imap.qq.com,使用SSL,端口號993
發送郵件服務器:smtp.qq.com,使用SSL,端口號465或587
賬戶名:您的QQ郵箱賬戶名(如果您是VIP帳號或Foxmail帳號,賬戶名需要填寫完整的郵件地址)
密碼:您的QQ郵箱密碼
電子郵件地址:您的QQ郵箱的完整郵件地址
涉及知識點
在本示例中,涉及知識點如下所示:
- imaplib模塊:此模塊實現通過IMAP【Internet Message Access Protocol,信息交互訪問協議】協議進行郵箱的登錄,接收和發送等功能。
- IMAP4_SSL(host='', port=IMAP4_SSL_PORT),通過此方法可以定義一個IMAP對象,需要對應的服務器和端口號。
- login(self, user, password),通過此方法實現對應郵箱的登錄,傳入指定的賬號,密碼即可。
- select(self, mailbox='INBOX', readonly=False) 選擇收件箱
- search(self, charset, *criteria) 查找獲取郵箱數據
- fetch(self, message_set, message_parts) 通過郵件編號,查找具體的郵件內容
- email模塊:此模塊主要用於郵件的解析功能
- message_from_string(s, *args, **kws) , 獲取解析數據消息體
- email.header.decode_header(msg.get('Subject'))[0][1] 解析編碼方式
- email.header.decode_header(msg.get('Date')) 解析郵件接收時間
- email.header.decode_header(msg.get('From'))[0][0] 解析發件人
- email.header.decode_header(msg.get('Subject'))[0][0].decode(msgCharset) 解析郵件標題
- email.utils.parseaddr(msg.get('Content-Transfer-Encoding'))[1] 解析郵件傳輸編碼
示例效果圖
示例分為兩部分,左邊是郵件列表,右邊是郵件內容,如下所示:
核心代碼
郵件幫助類,主要包括郵件的接收,具體郵件內容的解析等功能,如下所示:
1 import imaplib 2 import email 3 import datetime 4 5 6 class EmailUtil: 7 """ 8 Email幫助類 9 """ 10 host = 'imap.qq.com' # 主機IP或者域名 11 port = '993' # 端口 12 username = '********' # 用戶名 13 password = '**************' # 密碼或授權碼 14 imap = None # 郵箱連接對象 15 16 # mail_box = '**************' # 郵箱名 17 18 def __init__(self, host, port): 19 """初始化方法""" 20 self.host = host 21 self.port = port 22 # 初始化一個郵箱鏈接對象 23 self.imap = imaplib.IMAP4_SSL(host=self.host, port=int(self.port)) 24 25 def login(self, username, password): 26 """登錄""" 27 self.username = username 28 self.password = password 29 self.imap.login(user=self.username, password=self.password) 30 31 def get_mail(self): 32 """獲取郵件""" 33 # self.mail_box = mail_box 34 email_infos = [] 35 if self.imap is not None: 36 self.imap.select(readonly=False) 37 typ, data = self.imap.search(None, 'ALL') # 返回一個元組,data為此郵箱的所有郵件數據 38 # 數據格式 data = [b'1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18'] 39 if typ == 'OK': 40 for num in data[0].split(): 41 if int(num) > 10: 42 # 超過20,退出循環,不輸出 43 break 44 typ1, data1 = self.imap.fetch(num, '(RFC822)') # 通過郵箱編號和選擇獲取數據 45 if typ1 == 'OK': 46 print('**********************************begin******************************************') 47 msg = email.message_from_string(data1[0][1].decode("utf-8")) # 用email庫獲取解析數據(消息體) 48 # 獲取郵件標題並進行進行解碼,通過返回的元組的第一個元素我們得知消息的編碼 49 msgCharset = email.header.decode_header(msg.get('Subject'))[0][1] 50 # print('msg = ',msg) 51 # print('msgCharset= ',msgCharset) # gb2312 52 recv_date = self.get_email_date(email.header.decode_header(msg.get('Date'))) 53 mail_from = email.header.decode_header(msg.get('From'))[0][0] 54 if type(mail_from) == bytes: 55 mail_from = mail_from.decode(msgCharset) 56 57 mail_to = email.header.decode_header(msg.get('To'))[0][0] 58 subject = email.header.decode_header(msg.get('Subject'))[0][0].decode(msgCharset) # 獲取標題並通過標題進行解碼 59 60 print("Message %s\n%s\n" % (num, subject)) # 打印輸出標題 61 print('mail_from:' + mail_from + ' mail_to:' + mail_to + ' recv_date:' + str(recv_date)) 62 # # 郵件內容 63 # for part in msg.walk(): 64 # if not part.is_multipart(): 65 # name = part.get_param("name") 66 # if not name: # 如果郵件內容不是附件可以打印輸出 67 # print(part.get_payload(decode=True).decode(msgCharset)) 68 # print('***********************************end*****************************************') 69 email_info = { 70 "num": num, 71 "subject": subject, 72 "recv_date": recv_date, 73 "mail_to": mail_to, 74 "mail_from": mail_from 75 } 76 email_infos.append(email_info) 77 else: 78 print('請先初始化並登錄') 79 return email_infos 80 81 def get_email_content(self, num): 82 content = None 83 typ1, data1 = self.imap.fetch(num, '(RFC822)') # 通過郵箱編號和選擇獲取數據 84 if typ1 == 'OK': 85 print('**********************************begin******************************************') 86 msg = email.message_from_string(data1[0][1].decode("utf-8")) # 用email庫獲取解析數據(消息體) 87 print(msg) 88 # 獲取郵件標題並進行進行解碼,通過返回的元組的第一個元素我們得知消息的編碼 89 msgCharset = email.header.decode_header(msg.get('Subject'))[0][1] 90 # transfer_encoding = email.header.decode_header(msg.get('Content-Transfer-Encoding')) 91 transfer_encoding = email.utils.parseaddr(msg.get('Content-Transfer-Encoding'))[1] 92 print("transfer_encoding:",transfer_encoding) 93 print("charset:",msgCharset) 94 # 郵件內容 95 for part in msg.walk(): 96 if not part.is_multipart(): 97 name = part.get_param("name") 98 if not name: # 如果郵件內容不是附件可以打印輸出 99 if transfer_encoding == '8bit': 100 content = part.get_payload(decode=False) 101 else: 102 content = part.get_payload(decode=True).decode(msgCharset) 103 104 print(content) 105 print('***********************************end*****************************************') 106 return content 107 108 def get_email_date(self, date): 109 """獲取時間""" 110 utcstr = date[0][0].replace('+00:00', '') 111 utcdatetime = None 112 localtimestamp = None 113 try: 114 utcdatetime = datetime.datetime.strptime(utcstr, '%a, %d %b %Y %H:%M:%S +0000 (GMT)') 115 localdatetime = utcdatetime + datetime.timedelta(hours=+8) 116 localtimestamp = localdatetime.timestamp() 117 except: 118 try: 119 utcdatetime = datetime.datetime.strptime(utcstr, '%a, %d %b %Y %H:%M:%S +0800 (CST)') 120 localtimestamp = utcdatetime.timestamp() 121 except: 122 utcdatetime = datetime.datetime.strptime(utcstr, '%a, %d %b %Y %H:%M:%S +0800') 123 localtimestamp = utcdatetime.timestamp() 124 return localtimestamp 125 126 127 if __name__ == '__main__': 128 host = 'imap.qq.com' # 主機IP或者域名 129 port = '993' # 端口 130 username = '********' # 用戶名 131 password = '**************' # 密碼 132 mail_box = '**************' # 郵箱名 133 eamil_util = EmailUtil(host=host, port=port) 134 eamil_util.login(username=username, password=password) 135 eamil_util.get_mail() 136 print('done')
郵件展示類,主要用於郵件內容在前台頁面的展示,如下所示:
1 from tkinter import * 2 from tkinterie.tkinterIE import WebView 3 from test_email import EmailUtil 4 import time 5 import os 6 7 class Application(Frame): 8 email_util = None 9 total_line= 0 10 11 def __init__(self, master=None): 12 '''初始化方法''' 13 super().__init__(master) # 調用父類的初始化方法 14 host = 'imap.qq.com' # 主機IP或者域名 15 port = '993' # 端口 16 username = '*********' # 用戶名 17 password = '**************' # 密碼或授權碼 18 self.email_util = EmailUtil(host=host, port=port) 19 self.email_util.login(username=username, password=password) 20 self.master = master 21 # self.pack(side=TOP, fill=BOTH, expand=1) # 此處填充父窗體 22 self.create_widget() 23 24 def create_widget(self): 25 self.img_logo = PhotoImage(file="logo.png") 26 self.btn_logo = Button(image=self.img_logo , bg='#222E3C') 27 self.btn_logo.grid(row=0, column=0, sticky=N + E + W+S) 28 # 收件箱初始化 29 records = self.email_util.get_mail() 30 for i in range(len(records)): 31 # 時間特殊處理 32 recv_date = time.strftime("%Y-%m-%d", time.localtime(records[i]["recv_date"])) 33 subject = "{0} {1}".format(recv_date, records[i]["subject"]) 34 print(subject) 35 num = records[i]["num"] 36 btn_subject = Button(self.master, text=subject,height=2, width=30, bg=("#F0FFFF" if i%2==0 else "#E6E6FA"), anchor='w',command=lambda num=num: self.get_email_content(num) ) 37 btn_subject.grid(row=(i + 1), column=0, padx=2, pady=1) 38 # 明細 39 self.total_line=i 40 self.web_view = WebView(self.master, width=530, height=560) 41 self.web_view.grid(row=0, column=1, rowspan=(i+2), padx=2, pady=5, sticky=N + E + W) 42 43 def get_email_content(self,num): 44 """獲取郵件明細""" 45 content = self.email_util.get_email_content(num) 46 print(content) 47 if content.find('GBK')>0 or content.find('gbk')>0 or content.find('cnblogs')>0: 48 print('1-1111') 49 # content = content.encode().decode('gbk') 50 # print(content) 51 self.save_data(content) 52 abs_path = os.path.abspath("content.html") 53 self.web_view= WebView(self.master, width=530, height=560,url="file://"+abs_path) 54 self.web_view.grid(row=0, column=1, rowspan=(self.total_line + 2), padx=5, pady=5, sticky=N + E + W) 55 56 57 def save_data(self,content): 58 """保存數據""" 59 with open('content.html', 'w', encoding='utf-8') as f: 60 f.write(content) 61 62 63 if __name__ == '__main__': 64 root = Tk() 65 root.title('個人郵箱') 66 root.geometry('760x580+200+200') 67 root.setvar("bg", "red") 68 app = Application(master=root) 69 root.mainloop()
郵箱設置
如果要使用IMAP協議訪問郵箱服務進行收發郵件,則必須進行郵箱設置,路徑:登錄郵箱-->設置-->賬戶-->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服務,如下所示:
如果通過郵箱賬戶密碼登錄時,報如下錯誤,則表示需要通過授權碼進行登錄,如下所示:
溫馨提示:在第三方登錄QQ郵箱,可能存在郵件泄露風險,甚至危害Apple ID安全,建議使用QQ郵箱手機版登錄。
備注
逢雪宿芙蓉山主人
【作者】劉長卿【朝代】唐
日暮蒼山遠,天寒白屋貧。柴門聞犬吠,風雪夜歸人。