imaplib 模块登陆163邮箱及下载

来源:互联网 发布:ipad air2必备软件 编辑:程序博客网 时间:2024/04/27 04:24

上一篇使用了pop3模块进行邮件的下载,模块提供的功能不如本篇的imap。

本篇将稍微深入的通过imap进行邮件的登陆和下载;

1、邮件登陆信息获取:

#!/usr/bin/env python#-*- coding: utf-8 -*-#filename:receive_imap_email.pyimport imaplib, os, ConfigParser, reimport pprint#进行用户的登陆,主要通过config文件进行主机名,用户名和密码的获取def user_login(verbose=False):#verbose=False为函数参数定义的一种方式,在True的情况下执行    #Read the config file    config = ConfigParser.ConfigParser()    config.read(r'D:\Python\tmp\config.txt')    #Connect to the server    hostname = config.get('server', 'hostname')    if verbose:        print 'Connecting to', hostname        m = imaplib.IMAP4(hostname)    #Login to the account    username = config.get('account', 'username')    passwd = config.get('account', 'passwd')    if verbose:        print 'Logging in as ', username        m.login(username,passwd)        print type(m)#返回instance        return m    #对收件箱进行检查,列出对应的文件夹def list_inbox():    l_box = user_login(verbose=True)    print type(l_box)    try:        typ, data = l_box.list()#注意这种赋值方式,返回值是状态和inbox,drafts,sent,trash等;即后者的两个元素依次赋给前者        print 'Response code:', typ        print 'Response:'        pprint.pprint(data)    finally:        l_box.logout()#user_login(verbose=True)#在True的情况下执行list_inbox()

输出:

>>> ================================ RESTART ================================>>> Connecting to imap.163.comLogging in as  dxx_study<type 'instance'><type 'instance'>Response code: OKResponse:['() "/" "INBOX"', '(\\Drafts) "/" "&g0l6P3ux-"', '(\\Sent) "/" "&XfJT0ZAB-"', '(\\Trash) "/" "&XfJSIJZk-"', '(\\Junk) "/" "&V4NXPpCuTvY-"', '() "/" "&dcVr0mWHTvZZOQ-"', '() "/" "&Xn9USpCuTvY-"', '() "/" "&i6KWBZCuTvY-"', '() "/" "test"']

从以上可以看出,编码将是这个工作中的难点,因为邮件编码采用的是base64,并且还要区分发件人的编码,目前已遇到的主要有utf-8,这个一般不处理,直接输出,其次是gb2312,再次是gbk,或者英文的直接ascii。

2、邮件解码

对于解码部分又分为,邮件标题和发件人,以及邮件正文。

下面再实例记录下处理的一些方法和技巧:

#!/usr/bin/env python# -*- coding: utf-8 -*-#filename:receive_imap_email.pyimport imaplib, os, ConfigParser, reimport pprint, sys, email, string#reload(sys)#设置为命令窗口cmd输出#sys.setdefaultencoding('gbk')def user_login(verbose=False):#verbose=False为函数参数定义的一种方式,在True的情况下执行    #Read the config file    config = ConfigParser.ConfigParser()    config.read(r'D:\Python\tmp\config.txt')    #Connect to the server    hostname = config.get('server', 'hostname')    if verbose:        print 'Connecting to', hostname        m = imaplib.IMAP4(hostname)    #Login to the account    username = config.get('account', 'username')    passwd = config.get('account', 'passwd')    if verbose:        print 'Logging in as ', username        m.login(username,passwd)                return m    def list_inbox():#罗列收件箱的子目录    l_box = user_login(verbose=True)    try:        typ, data = l_box.list()#注意这种赋值方式,返回值是状态和inbox,drafts,sent,trash等        print 'Response code:', typ        print 'Response:'        pprint.pprint(data)#Mailbox names are in modified UTF7 character set.输出会出现乱码    finally:        l_box.logout()def status_mailbox():    stat_m = user_login(verbose=True)    try:        typ, data = stat_m.list()        for line in data:#由于字符编码问题,暂无法去匹配对应收件箱的状态,未完待续           print line    finally:        stat_m.logout()def to_unicode(s, encoding):    if encoding:        return unicode(s, encoding)    else:        return unicode(s)def parse_email(msg):    for part in msg.walk():        if not part.is_multipart():            content_type = part.get_content_type()            filename = part.get_filename()            charset = part.get_content_charset()                        if charset == None or charset == 'utf-8':                return part.get_payload()            elif charset == 'gb2312':                return part.get_payload(decode=True).decode('gb2312').encode('utf-8')            else:                return unicode(part.get_payload(decode=True),charset).encode('utf-8') def imap_example():    m = user_login(verbose=True)    m.select()    result, message = m.select()    i = 1    try:        typ, data = m.search(None, 'All')#Search mailbox for matching messages        #for num in data[0].split():#与下方等价        for num in string.split(data[0]):            typ, data = m.fetch(num, '(RFC822)')#Fetch (parts of) messages            msg = email.message_from_string(data[0][1])            from_sender = msg['From'].split(' ')            if(len(from_sender) == 2):                fromname = email.Header.decode_header((from_sender[0]).strip('\"'))                sender = 'E-mail From1: ' + to_unicode(fromname[0][0], fromname[0][1]) + from_sender[1]            else:                sender = 'E-mail From2: ' + msg['From']            date = 'Date From: ' + msg['Date']            subject = email.Header.decode_header(msg['Subject'])            if subject[0][1] == 'utf-8':                title = 'Subject: ' + subject[0][0]                mail_content = parse_email(msg)            else:                title = 'Subject: ' + to_unicode(subject[0][0], subject[0][1])                mail_content = parse_email(msg)            print '\n'            print sender,'\n',date,'\n',title            #邮件保存            outf = open('%s.eml' % i, 'w')            outf.write(mail_content)            outf.close()             i = i + 1    finally:        m.logout()imap_example()

parse_email(msg)

imap_example()

to_unicode(s, encoding)

这三个函数的主要功能依次是,

1、利用email模块解析邮件,实际是进行邮件正文的编码转换和信息提取

2、利用email的email.Header进行邮件sender,subject,date的提取和输出

3、字符编码转换。(这个还不是很理解)

输出:

>>> ================================ RESTART ================================>>> Connecting to imap.163.comLogging in as  dxx_studyE-mail From1: 推ぁ校园居士<tuitui-xkq@qq.com> Date From: Wed, 24 Mar 2010 22:39:36 +0800 Subject: 推E-mail From1: 幻影<503436483@qq.com> Date From: Thu, 25 Mar 2010 22:49:10 +0800 Subject: 挂科人次E-mail From1: 贾苏川<jiasuchuan@suse.edu.cn> Date From: Thu, 25 Mar 2010 23:39:11 +0800 Subject: 挂科人次.......等

这样就不会出现前面提到的乱码了。

另外,对于邮件的下载结果如下:


虽然邮件下载下来了,但是当初想以发件人或者subject来命名邮件的,实际中由于win的命名规则让有些邮件的sender或者subject根本就不能直接作为文件名,要使用,肯定需要进一步检查和处理;

其次,本次下载下来的邮件打开查看时,没有标题时间,只有内容,实际效果不如直接

for msg_id in p.list()[1]:    print msg_id    outf = open('%s.eml' % msg_id, 'w')    outf.write('\n'.join(p.retr(msg_id)[1]))    outf.close()
进行保存的效果好,而且出现乱码的机会也少。

实际使用中可以结合着使用。

3、其它参考及下载附件

对于email模块和解码还参考了下面一个程序:

实际和上面功能差不多,都可以提取date,subject,sender,但是可以下载附件,不过邮件正文保存有问题,不能用于保存邮件。

#!/usr/bin/env python# -*- coding: utf-8 -*-#filename:receive_mail.pyimport imaplib, email, sys#reload(sys)#sys.setdefaultencoding('gbk')def savefile(filename, data, path):#保存文件的方式和位置设置    try:        filepath = path + filename        print 'Saved as ' + filepath#保存路径设置        f = open(filepath, 'w')        f.write(data)#写入数据        f.close()    except:        print ('filename input error')def my_unicode(s, encoding):#字符编码转换    if encoding:        return unicode(s, encoding)    else:        return unicode(s)def get_charset(message, default='ascii'):#获取字符编码/但对utf-8需要验证    return message.get_charset    return defaultdef parse_email(msg, mypath):#解析邮件,区分正文和附件    mail_content =  None    content_type = None    suffix = None    for part in msg.walk():#email.message.Message模块中的walk()方法,用于深度遍历信息树对象        if not part.is_multipart():#email模块中 .is_multipart(),Return True if the message’s payload is a list of sub-Message objects,                                   #otherwise return False. When is_multipart() returns False, the payload should be a string object.                                   #这里表示,如果message payload是一个string object时进行如下处理            content_type = part.get_content_type()#get_content_type(),Return the message’s content type. The returned string is coerced to lower case of the form maintype/subtype.                                                    #If there was no Content-Type header in the message the default type as given by get_default_type() will be returned.            filename = part.get_filename()#Return the value of the filename parameter of the Content-Disposition header of the message.            #charset = get_charset(part)#调用get_charset()函数,获取字符编码            ch = part.get_content_charset()                        if filename :#check the attachment,进行附件处理                h = email.Header.Header(filename)#Create a MIME-compliant header that can contain strings in different character sets.                dh = email.Header.decode_header(h)#Decode a message header value without converting the character set. The header value is in header.                                                #dh得到的返回值是一个list,即(decoded_string, charset),包含header的decoded parts                fname = dh[0][0]                encodeStr = dh[0][1]#分别取值                if encodeStr != None:                    #if charset == None:                    if ch == None:                        fname = fname.decode(encodeStr, 'gbk')                    else:                        #fname = fname.decode(encodeStr, charset)#比较dh返回的charset和part中get得到的charset,然后根据类型解码                        fname = fname.decode(encodeStr, ch)                data = part.get_payload(decode=True)#Return the current payload, which will be a list of Message objects when is_multipart() is True,                                                    # or a string when is_multipart() is False.                print ('Attachment:' + fname)                if fname != None or fname != '':#如果存在dh[0][0],则save attachment                    savefile(fname, data, mypath)#调用保存函数            else:                if content_type in ['text/plain']:#进行message的内容格式判断,并选择后缀名保存方式                    suffix = '.txt'                if content_type in ['text/html']:                    suffix = '.htm'                #if charset == None:                #    mail_content = part.get_payload(decode=True)                #else:                #    mail_content = part.get_payload(decode=True).decode(charset)#对utf-8编码邮件会出错                ##ch = part.get_content_charset()                if ch:                    mail_content = unicode(part.get_payload(decode=True),ch).encode('utf-8')                else:                    mail_content = part.get_payload(decode=True).decode('gb2312').encode('utf-8')    return (mail_content, suffix)def get_mail(mailhost, account, password, mypath, port = 993, ssl = 1):#获取邮件        if ssl == 1:#选择是否采用 SSL login         imap_server = imaplib.IMAP4_SSL(mailhost,port)    else:        imap_server = imaplib.IMAP4(mailhost,port)    imap_server.login(account, password)    s = imap_server.select()#选择邮件箱,默认为收件箱,返回收件箱邮件数目,必须选择!!                            #print s #得到信息为:('OK', ['178'])        number = 1#定义编号,方便终端输出显示如 No:3     #Message statues = 'All,Unseen,Seen,Recent,Answered,Flagged'#邮件状态设置,定义从哪里提取邮件    resp, items = imap_server.search(None, 'Unseen')#选择未读收件箱    for i in items[0].split():        resp, data = imap_server.fetch(i, '(RFC822)')#获取parts of message        msg = email.message_from_string(data[0][1])#提取list中第二部分信息,msg为一个instance                whosend = msg['From'].split(' ')#以空格拆分from:sender的信息内容,得到一个list,长度为2,list[1]为发送者的邮箱        if(len(whosend) == 2):            fromname = email.Header.decode_header((whosend[0]).strip('\"'))#先移除双引号,再解码            strfrom = 'From:' + my_unicode(fromname[0][0], fromname[0][1]) + whosend[1]#调用my_unicode()函数进行编码        else:            strfrom = 'From:' + msg['From']        strdate = 'Date:' + msg['Date']        subject = email.Header.decode_header(msg['Subject'])#得到一个list        #print subject[0][0]#邮件标题        #print subject[0][1]#字符编码格式,英文为None,汉字为gbk,或者utf-8时,后续处理有问题        #此处实际有缺陷,加一个检测,如果是utf-8则不进行转换        if subject[0][1] != 'utf-8':            sub = my_unicode(subject[0][0], subject[0][1])#调用my_unicode()进行编码转换            strsub = 'Subject:' + sub            mail_content, suffix = parse_email(msg, mypath)#调用parse_email()进行邮件解析                    else:            strsub = 'Subject:' + subject[0][0]            mail_content, suffix = parse_email(msg, mypath)#调用parse_email()进行邮件解析                #提取的邮件信息输出        print '\n'        print 'No:' + str(number)        print strfrom        print strdate        print strsub        '''        print 'content:'        print mail_content        '''        #保存邮件正文,编码还存在问题        if (suffix != None and suffix !='') and (mail_content != None and mail_content !=''):            savefile(str(number) + suffix, mail_content, mypath)#调用savefile()函数进行邮件保存        number = number + 1    imap_server.close()    imap_server.logout()if __name__ == '__main__':    mypath = "D:\\test_tmp\\" #邮件保存位置    print 'getting e-mail ...'    #get_mail('imap.gmail.com','julius.luck@gmail.com','XXXX',mypath,993,1)    get_mail('imap.163.com','dxx_study@163.com','XXXX',mypath,143,0)    print 'download e-mail completed'

输出:

>>> ================================ RESTART ================================>>> getting e-mail ...No:1From:人人网<renrenedm@xiaonei.com>Date:Fri, 1 Nov 2013 23:15:17 +0800 (CST)Subject:王沛: 小伙伴们在关注什么?尽在人人热门分享一周盘点Saved as D:\test_tmp\1.htmNo:2From:王海<dxx_study@163.com>Date:Tue, 5 Nov 2013 15:11:59 +0800 (CST)Subject:testt mailSaved as D:\test_tmp\2.htmNo:3From:王海<dxx_study@163.com>Date:Thu, 7 Nov 2013 09:49:47 +0800 (CST)Subject:测试汉字Saved as D:\test_tmp\3.htm    Attachment:hosts.txt         #这里保存了一个附件,注意Saved as D:\test_tmp\hosts.txtNo:4From:王海<dxx_study@163.com>Date:Thu, 7 Nov 2013 14:06:31 +0800 (CST)Subject:test attachmentSaved as D:\test_tmp\4.htmdownload e-mail completed>>> 

4、以此类推

这个程序读取的是未读邮件,实际是可以根据需求定义的,也可以是已读邮件,标志邮件等

 #Message statues = 'All,Unseen,Seen,Recent,Answered,Flagged'#邮件状态设置,定义从哪里提取邮件    resp, items = imap_server.search(None, 'Unseen')#选择未读收件箱
可以设置上面的参数。


5、Python email模块

对于email模块,时间参数还是比较多的,没有去像正则那样仔细看官方文档了,回顾下折腾的最多的还是字符编码这一块,主要是邮件的base64和使用中文导致的,如果都是英文会好很多。

下面粘贴下email模块的相关解释

email.message_from_string() 这个方法能把String的邮件转换成email.message实例.
下面这样调用:
mail=email.message_from_string(string.join(message,'\n'))
这样就生成了一个email.Message实例

提取邮件内容,和标题,mail支持字典操作,比如下面的操作:
mail['subject'] ,mail.get('subject')
mail['To'],mail.get('to')'
mail.keys() ,mail.items() 等等.

中文邮件的标题和内容都是base64编码的,解码可以使用email.Header 里的decode_header()方法:
比如 print mail['subject']   显示的都未处理的编码.
'=?GB2312?B?UmU6IFtweXRob24tY2hpbmVzZV0g?=\n\t=?GB2312?B?y63E3LDvztLV0tbQzsS1xFBZVEhPTrP10afRp8+wtcTXysHP?='

email.Header.decode_header(mail['subject']) 下面是解码后的信息.
[('Re: [python-chinese] \xcb\xad\xc4\xdc\xb0\xef\xce\xd2\xd5\xd2\xd6\xd0\xce\xc4\xb5\xc4PYTHON\xb3\xf5\xd1\xa7\xd1\xa7\xcf\xb0\xb5\xc4\xd7\xca\xc1\xcf', 'gb2312')]
返回的是一个列表,里面的内容保存在一个元组里。(解码后的字串,字符编码)

显示解码后的标题就象下面这样
print email.Header.decode_header(mail['subject'])[0][0]
Re: [python-chinese] 谁能帮我找中文的PYTHON初学学习的资料

上面的mail标题编码是'gb2312'的。如果编码是别的比如'utf-8'编码,那么显示出来的就是乱码了,需要使用unicode()方法,unicode('string',‘编码,比如UTF-8'),比如
subject=email.Header.decode_header(mail['subject'])[0][0]
subcode=email.Header.decode_header(mail['subject'])[0][1])
print unicode(subject,subcode)
Re: [python-chinese] 谁能帮我找中文的PYTHON初学学习的资料

如何处理邮件内容:
mail里有很多方法,熟悉这些方法处理邮件就很容易了。
get_payload() 这个方法可以把邮件的内容解码并且显示出来。第一个可选择参数是mail实例,第二个参数是decode='编码' ,一般都是,'base64'编码
is_multipart(),这个方法返回boolean值,如果实例包括多段,就返回True,
print mail.is_multipart()
true  ,这说明这个mail邮件包含多个字段。


MIME (Multipurpose Internet Mail Extensions) (RFC 1341)
  MIME扩展邮件的格式,用以支持非ASCII编码的文本、非文本附件以及包含多个部分 (multi-part) 的邮件体等。


1. class email.message.Message

__getitem__,__setitem__实现obj[key]形式的访问。
Msg.attach(playload): 向当前Msg添加playload。
Msg.set_playload(playload): 把整个Msg对象的邮件体设成playload。
Msg.add_header(_name, _value, **_params): 添加邮件头字段。

2. class email.mime.base.MIMEBase(_maintype, _subtype, **_params)
  所有MIME类的基类,是email.message.Message类的子类。

3. class email.mime.multipart.MIMEMultipart()
  在3.0版本的email模块 (Python 2.3-Python 2.5) 中,这个类位于email.MIMEMultipart.MIMEMultipart。
  这个类是MIMEBase的直接子类,用来生成包含多个部分的邮件体的MIME对象。

4. class email.mime.text.MIMEText(_text)
  使用字符串_text来生成MIME对象的主体文本。

另外:

邮件格式 (RFC 2822)
  每封邮件都有两个部分:邮件头和邮件体,两者使用一个空行分隔。

  邮件头每个字段 (Field) 包括两部分:字段名和字段值,两者使用冒号分隔。有两个字段需要注意:From和Sender字段。From字段指明的是邮件的作者,Sender字段指明的是邮件的发送者。如果From字段包含多于一个的作者,必须指定Sender字段;如果From字段只有一个作者并且作者和发送者相同,那么不应该再使用Sender字段,否则From字段和Sender字段应该同时使用。

  邮件体包含邮件的内容,它的类型由邮件头的Content-Type字段指明。RFC 2822定义的邮件格式中,邮件体只是单纯的ASCII编码的字符序列。


6、总结:

     从这一次练习可以看出

1.充分利用已有的模块进行调用,可以起到事半功倍的作用,免得自己在那里解码;

2.解码与编码也是一个学问,特别是对处理邮件,不同客户端和服务端,语言涉及的太多,情况比较复杂;

3.了解了邮件的基本格式,对于处理对象就是要按照其自己的规则来处理的思想;



7、参考文献:

http://pymotw.com/2/imaplib/ 这篇很好,不过从正则匹配编码开始就不能套用了,别个是英文呀!自己的是中文,不过处理方式及基本解释还是很实用的     。

http://www.cs.sfu.ca/CourseCentral/120/havens/python-2.7-docs-html/library/email.message.html       email模块

最后一个程序,源自新浪,记不得了,里面有很多错误,自己修改了很多。

http://blog.csdn.net/bravezhe/article/details/7659173

http://tools.ietf.org/html/rfc3501.html

好了,就这么多,折腾了两三天了,到此结束,毕竟只是瞧瞧email ,非专攻!





原创粉丝点击