• 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
    %s
    " % (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邮箱手机版登录。

    备注

    逢雪宿芙蓉山主人

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

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


    作者:Alan.hsiang
    出处:http://www.cnblogs.com/hsiang/
    本文版权归作者和博客园共有,写文不易,支持原创,欢迎转载【点赞】,转载请保留此段声明,且在文章页面明显位置给出原文连接,谢谢。
    关注个人公众号,定时同步更新技术及职场文章

  • 相关阅读:
    对网页图片的增删改管理
    还没搞完的排序(后期更新)
    web实现图片动态
    C++11 笔记
    如何解决刷新系统桌面响应速度很慢的问题
    CGrowableArray解析 _ DXUT容器
    测试...外部指针访问private
    CustomUI Direct3D9_Sample
    缺少.lib文件导致的Link2019 解决方案汇总
    在DirectX9中使用DXUT定制按钮来控制模型旋转的问题
  • 原文地址:https://www.cnblogs.com/hsiang/p/15306652.html
Copyright © 2020-2023  润新知