Python通過IMAP實現郵箱客戶端


概述

在日常工作生活中,都是利用個人或公司的郵箱客戶端進行收發郵件,那么如何打造一款屬於自己的郵箱客戶端呢?本文以一個簡單的小例子,簡述如何通過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郵箱手機版登錄。

備注

逢雪宿芙蓉山主人

【作者】劉長卿【朝代】唐

日暮蒼山遠,天寒白屋貧。柴門聞犬吠,風雪夜歸人。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM