概述
在日常工作生活中,都是利用个人或公司的邮箱客户端进行收发邮件,那么如何打造一款属于自己的邮箱客户端呢?本文以一个简单的小例子,简述如何通过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邮箱手机版登录。
备注
逢雪宿芙蓉山主人
【作者】刘长卿【朝代】唐
日暮苍山远,天寒白屋贫。柴门闻犬吠,风雪夜归人。