Python通过IMAP实现邮箱客户端

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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的通用配置如下:

  1. 接收邮件服务器:imap.qq.com,使用SSL,端口号993
  2. 发送邮件服务器:smtp.qq.com,使用SSL,端口号465或587
  3. 账户名:您的QQ邮箱账户名(如果您是VIP帐号或Foxmail帐号,账户名需要填写完整的邮件地址)
  4. 密码:您的QQ邮箱密码
  5. 电子邮件地址:您的QQ邮箱的完整邮件地址

涉及知识点

在本示例中,涉及知识点如下所示:

  • imaplib模块:此模块实现通过IMAP【Internet Message Access Protocol,信息交互访问协议】协议进行邮箱的登录,接收和发送等功能。
  1. IMAP4_SSL(host='', port=IMAP4_SSL_PORT),通过此方法可以定义一个IMAP对象,需要对应的服务器和端口号。
  2. login(self, user, password),通过此方法实现对应邮箱的登录,传入指定的账号,密码即可。
  3. select(self, mailbox='INBOX', readonly=False) 选择收件箱
  4. search(self, charset, *criteria) 查找获取邮箱数据
  5. fetch(self, message_set, message_parts) 通过邮件编号,查找具体的邮件内容
  • email模块:此模块主要用于邮件的解析功能
  1. message_from_string(s, *args, **kws) , 获取解析数据消息体
  2. email.header.decode_header(msg.get('Subject'))[0][1] 解析编码方式
  3. email.header.decode_header(msg.get('Date')) 解析邮件接收时间
  4. email.header.decode_header(msg.get('From'))[0][0] 解析发件人
  5. email.header.decode_header(msg.get('Subject'))[0][0].decode(msgCharset) 解析邮件标题
  6. 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邮箱手机版登录。

备注

逢雪宿芙蓉山主人

【作者】刘长卿【朝代】唐

日暮苍山远,天寒白屋贫。柴门闻犬吠,风雪夜归人。

相关文章
|
6月前
|
存储 Python
Python网络编程基础(Socket编程)UDP客户端编程
【4月更文挑战第9天】在UDP通信中,客户端负责发送数据到服务器,并接收来自服务器的响应。与服务器不同,客户端通常不需要绑定到特定的地址和端口,因为它可以临时使用任何可用的端口来发送数据。下面,我们将详细讲解UDP客户端编程的基本步骤。
|
6月前
|
网络协议 Python
python中socket客户端发送和接收数据
【4月更文挑战第7天】本教程聚焦TCP客户端数据发送与接收。使用Python的`socket`模块,通过`send()`发送字节串至服务器,如`client_socket.send(message_bytes)`;用`recv()`接收数据,如`received_data = client_socket.recv(buffer_size)`。异常处理确保网络错误时程序健壮性,例如`try-except`捕获`socket.error`。理解和掌握这些基础操作对于构建稳定的TCP客户端至关重要。
1364 1
|
6月前
|
网络协议 安全 Python
python中socket客户端关闭连接
【4月更文挑战第7天】本教程介绍了如何在TCP客户端中正确关闭连接。使用`close()`方法可关闭Socket连接并释放资源,示例代码显示了在正常和异常情况下关闭连接的方法。注意异常处理以确保在任何情况下都能关闭连接,并避免并发操作同一Socket,以保证连接的稳定和安全。掌握这些技巧对编写健壮的TCP客户端至关重要。
|
1月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
140 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
1月前
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
153 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
5月前
|
XML 物联网 API
服务端和客户端 RESTful 接口上传 Excel 的 Python 代码
本文作者木头左是物联网工程师,分享如何使用 Python 和 Flask-RESTful 构建一个简单的 RESTful API,实现文件上传功能,特别支持Excel文件。通过安装Flask和Flask-RESTful库,创建Flask应用,实现文件上传接口,并将其添加到API。该方法具有简单易用、灵活、可扩展及社区支持等优点。
服务端和客户端 RESTful 接口上传 Excel 的 Python 代码
|
2月前
|
关系型数据库 MySQL Python
mysql之python客户端封装类
mysql之python客户端封装类
|
3月前
|
网络协议 安全 Unix
6! 用Python脚本演示TCP 服务器与客户端通信过程!
6! 用Python脚本演示TCP 服务器与客户端通信过程!
|
3月前
|
传感器 数据采集 算法
python实现ModBusRTU客户端方式
python实现基于串口通信的ModBusRTU客户端是一件简单的事情,只要通过pymodbus模块就可以实现。
|
3月前
|
开发者 Python
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
89 1
下一篇
无影云桌面