1. 程式人生 > >python傳送、解析郵件

python傳送、解析郵件

參考資料

http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=575710

總體來說python處理郵件還是比較方便的,庫提供了很多工具.下面我把心得寫出來,給新手一個啟迪,也請高手給些更好的方法. 

先說接受郵件.  poplib 方法. 
1.poplib.POP3('這裡填入你pop郵件伺服器地址') 登陸伺服器. 
2.poplib.user('使用者名稱 ') poplib.pass_('密碼') 
3.poplib.stat()方法返回一個元組:(郵件數,郵件尺寸) 
   mailCount,size=poplib.stat() 
   這樣mailCount就是郵件的數量,size,就是所有郵件的大小. 


3.poplib.rert('郵件號碼')方法返回一個元組:(狀態資訊,郵件,郵件尺寸)  
  hdr,message,octet=server.retr(1) 讀去第一個郵件資訊. 
   hdr的內容就是響應資訊和郵件大小比如'+OK 12498 octets' 
   message 是包含郵件所有行的列表. 
   octet 是這個郵件的內容. 


 得到的message是郵件的原始內容,也就是沒有解碼過的,裡面的內容和標題基本上都是base64編碼的,下面說說如何處理原始郵件. 
python 的email庫裡提供了很多處理郵件的方法,我們先把原始郵件轉成email例項,這樣就可以用庫方法處理郵件. 
email.message_from_string() 這個方法能把String的郵件轉換成email.message例項. 
比如我們上面的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'的,在我的winxp機器上可以直接顯示,如果編碼是別的比如'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郵件包含多個欄位。我下面的函式就可以處理,顯示郵件的全部內容。 


def showmessage(mail):


    if mail.is_multipart():


        for part in mail.get_payload():


            showmessage(part)


    else:


        type=mail.get_content_charset()


        if type==None:


            print mail.get_payload()


        else:


            try:


                print unicode(mail.get_payload('base64'),type)


            except UnicodeDecodeError:


                print mail






最後,有點要說明,如果郵件裡的中文用mail.Header.decode_header()方法,和unicode()方法都不能正常顯示,那麼說明這個中文無法處理了,顯示出來就是亂碼.比如:看看看見,最終處理完成後,還是亂麻。 


>;>;>;mail.get('subject')


'Re: [python-chinese] =?UTF-8?B?wrnDmMOTw5p4bWzCscOgw4LDq8K1w4TDjg==?=\n\t=?UTF-8?B?w4rDjMOi?='


>;>;>;decode_header( mail.get('subject'))


[('Re: [python-chinese]', None), ('\xc2\xb9\xc3\x98\xc3\x93\xc3\x9axml\xc2\xb1\xc3\xa0\xc3\x82\xc3\xab\xc2\xb5\xc3\x84\xc3\x8e\xc3\x8a\xc3\x8c\xc3\xa2', 'utf-8')]


>;>;>;print decode_header( mail.get('subject'))[1][0]


鹿脴脫脷xml滷脿脗毛碌脛脦脢脤芒


>;>;>;print unicode(decode_header( mail.get('subject'))[1][0],'utf-8')


1?óúxml±à??μ??êìa








 jasonnbfan 回覆於:2005-07-11 20:22:35


下面說說傳送郵件,其實我感覺發送比接收郵件要容易。還是使用mail.Message裡的方法。我們一步一步來。 
1:傳送一個普通的文字郵件。 
msg=mail.Message.Message()    #一個例項 
msg['to']='
[email protected]
'      #傳送到哪裡 
msg['from']='[email protected]'       #自己的郵件地址 
msg['date']=time.ctime()             #時間日期 
msg['subject']=email.Header.Header('郵件主題','gb2312') 
#這裡用Header方法處理subject. 
完成後的樣子. 
>;>;>;print msg.as_string() 
to: [email protected] 
from: [email protected]
 
date: Mon Jul 11 20:18:13 2005 
subject: =?gb2312?b?08q8/tb3zOI=?=  


下面開始寫內容。 
body=email.MIMEText.MIMEText('這裡是郵件內容',_subtype='plain',_charset='gb2312')




MIMEText()方法包括3個引數,內容,_subtype型別,_charset字元編碼,完成後的樣子: 
>;>;>;print body.as_string() 
Content-Type: text/plain; charset="gb2312" 
MIME-Version: 1.0 
Content-Transfer-Encoding: base64 


1eLA78rHxNrI3Q== 


Content-Type,說明內容型別,這裡是txt/plain,純文字型別。如果新增附件 
那麼就是Application/octet-stream 
Content-Transfer-Encoding這個就是編碼型別,這裡是base64,現在的email都是base64編碼 


寫完以後如何組合起來?mail有一個as_string()方法,顧名思義。顯示成一個字串.我上面也用了。smtplib裡的sendmail()方法裡需要的是字串型別。所以我們這裡可以這樣: 完整的內容加起來就行了。 
>;>;>;print msg.as_string()+body.as_string() 
to:
[email protected]
 
from: [email protected] 
date: Mon Jul 11 20:18:13 2005 
subject: =?gb2312?b?08q8/tb3zOI=?= 


Content-Type: text/plain; charset="gb2312" 
MIME-Version: 1.0 
Content-Transfer-Encoding: base64 


1eLA78rHxNrI3Q== 


如何傳送. 
server=smtplib.SMTP('smtp.mail.yahoo.com')  #你傳送伺服器的地址 
server.login('username','password')       #使用者名稱和密碼 
server.sendmail('from','to','msg.as_string()[:-1]+body.as_string()') 
#這樣就完成了郵件的傳送. 
有一點要注意.只有內容前面有一個空行,其他的地方都不能有空行.前面我們直接print msg.as_string()+body.as_string()可以看見在subject: 和Content-type:這裡有一個空行,所以我用'msg.as_string()[:-1]把多餘的空行去掉.讓他和body保持格式. 


下面看看如何髮帶附件的郵件,和上面差不多. 
attach=mail.MIMEMultipart.MIMEMultipart()  #這裡建立一個帶附件的例項 
attach.attach(body)  #把我們剛才寫的郵件內容加進去. 


attachment=MIMEText(open('myself.py','r').read(),'base64') 
#第一個引數開啟檔案read()方法讀出所有內容,剛好是字串格式,第二個引數是希望的編碼,這種方法比較簡單. 


attachment.replace_header('Content-type','Application/octet-stream;name="myself.py"') 
#前面講過Content-type:他的值可以是text/plain text/heml 等等,如果是附件,就是Application/octet-stream,後面的;name="myself.py"是附件的檔名.預設的MIMEText()後這裡的內容是text/plain的,所以需要替換 


attachment.add_header('Content-Disposition','attachment;filename="myself.py") 
#這裡新增一個標題,Content-Disposition,attachment說明是一個附件,filename說明檔名.mail裡有一個get_filename()的方法可以得到附件裡的檔名. 


attach.attach(attachment)  #現在我們把編碼好的附件也加進去 


完成後的郵件像下面這樣 


to: [email protected]


from: [email protected]


subject: =?gb2312?b?1vfM4g==?=


Content-Type: multipart/mixed; boundary="===============0572491976=="


MIME-Version: 1.0






--===============0572491976==


Content-Type: text/plain; charset="gb2312"


MIME-Version: 1.0


Content-Transfer-Encoding: base64






1eLA78rHxNrI3Q==






--===============0572491976==


Content-Type: Application/octet-stream;name="myself.py"


MIME-Version: 1.0


Content-Transfer-Encoding: base64


Content-Disposition: attachment;filename="myself.py"






ZnJvbSBlbWFpbC5NSU1FVGV4dCBpbXBvcnQgTUlNRVRleHQKZnJvbSBlbWFpbC5NSU1FTXVsdGlw


YXJ0IGltcG9ydCBNSU1FTXVsdGlwYXJ0CmZyb20gZW1haWwuSGVhZGVyIGltcG9ydCBIZWFkZXIK


ZnJvbSBlbWFpbC5IZWFkZXIgaW1wb3J0IGRlY29kZV9oZWFkZXIKZnJvbSB0eXBlcyBpbXBvcnQg


--===============0572491976==--






好了,可以傳送了. 
server=smtplib.SMTP('smtp.mail.yahoo.com')   
server.login('username','password')        
server.sendmail('from','to','msg.as_string()[:-1]+attach.as_string()') 


剛才說了,附件也可以不用MIMEText()方法建立像下面這樣也可以. 
att=base64.encodestring(open('file','r').read()) 
att=MIMEText(att) 
然後就和前面一樣,換標題Content-type, 加Content-Disposition標題,等等.顯然比較麻煩. 


當然更簡單的方法就是建立上面的attach以後,直接在attach裡新增 主題等標題. 
attach['to']='[email protected]
attach['from']='[email protected]
attach['date']=time.ctime() 
attach['subject']=Header('直接傳送的標題','gb2312') 
這樣新增完以後直接attach.as_string()傳送就可以了,包括了主題,內容,附件. 


全文完,菜鳥學習經過,僅供新手參考. 


希望高手能多多指點.


 jasonnbfan 回覆於:2005-07-11 21:40:33


發現自己的表達能力太差,看來要多練練,大家將就著看好了。 
這個星期研究Tk學會了寫個GUI介面的郵件程式.學好了再上來寫心得.


 jasonnbfan 回覆於:2005-07-11 21:44:34


最後把我寫的一個簡陋的,幼稚的一個字元平臺的email程式貼上來.希望高手能給指點指點. 
程式的選單截面根據Programming Python ed2,裡面的程式改的,否則我肯定是想不出來這樣的選單截面.(想象力差,還是經驗不足,暈) 


儲存郵件方法沒有做出來.傳送郵件也沒寫.
from email.MIMEText import MIMEText


from email.MIMEMultipart import MIMEMultipart


from email.Header import Header


from email.Header import decode_header


from types import *


import smtplib,poplib,string,sys,os,email










helptext = """


Available commands:


i     - index display


l n?  - list all messages (or just message n)


d n?  - mark all messages for deletion (or just message n)


s n?  - save input num messages to a file (or just message n)


m     - compose and send a new mail message


q     - quit pymail


?     - display this help text


"""


#簡單的選單處理,無返回值,要求一個處理過的mail列表


def interact(processmail):


    #showindex(processmail)


    while 1:


        try:


            command=raw_input('[Pymail] Action? (i, l, d, s, m, q, ?) ')


        except EOFError:


            command='q'






        if command=='q' or not command:


            break






        elif command[0]=='i':


            showindex(processmail)






        elif command[0]=='l':


            if len(command)==1:


                for mail in processmail:


                    showmessage(mail)


                    print string.join(message)


            else:


                if 0<msgnum(command)<=len(processmail):


                    num=msgnum(command)


                    showsubject(processmail[num-1])


                    showmessage(processmail[num-1])


        elif command[0]=='s':


            if len(command)==1:


                print '請輸入要儲存的郵件號碼'


                continue


            else:


                if 0<msgnum(command)<=len(processmail):


                    num=msgnum(command)


                    savemail(processmail[num-1])






        elif command[0]=='?':


            print helptext






        else:


            print 'What? -- type "?" for commands help'






 #儲存email未完成           


def savemail(mail):


    filename=raw_input('Enter a file name:')


    file=open('filename','w')


    print >;>; file,showsubject(mail),showmessage(mail)


    print 'saving mail to %s ok.' %(filename)






#處理輸入的數字   


def msgnum(command):


    try:


        return string.atoi(string.split(command)[1])


    except:


        return -1






#用於接收 郵件的相關處理,返回一個server例項   


def POPconnect():


    sname,user,passwd=popconfig()


    server=poplib.POP3(sname)


    server.user(user)


    server.pass_(passwd)


    print server.getwelcome()


    return server






#用於傳送 郵件的相關處理,返回一個server例項 


def SMTPconnect():


    server=smtplib.SMTP(sname)


    server.login(user,passwd)


    return server






#從伺服器讀取郵件到maillist.列表,位處理的原始字串


def loadmail():


    server=POPconnect()


    try:


        print server.list()


        (mailCount,mailByte)=server.stat()


        print 'There are',mailCount,'mail messages in',mailByte,'bytes'


        print 'Retrieving:'


        mailList=[]


        for i in range(mailCount):


            print i+1,


            (hdr,message,octet)=server.retr(i+1)


            mailList.append(string.join(message,'\n'))


        print


        assert len(mailList)==mailCount


        return mailList






    finally:


        server.quit()






#處理loadmain返回的原始mail列表,返回處理過的processmail列表


def processmail(mailList):


    processmaillist=[]


    for i in range(len(mailList)):


        processmaillist.append(email.message_from_string(mailList))


    return processmaillist






#顯示郵件主題,要求一個處理過的mail做引數


def showsubject(mail):


    header=[]


    for head in decode_header(mail.get('subject')):


        if head[1]=='utf-8':


            header.append(unicode(head[0],'utf-8'))


        else:


            header.append(head[0])


            


    for sub in ('From','Date','Subject'):


        if sub=='Subject':


            print 'Subject:',


            for subject in header:


                try:


                    print subject,


                except UnicodeEncodeError:


                    print '注意:這個郵件標題無法正常顯示...'


        else:


            print '%s:%s' %(sub,mail[sub])


    print      


        


#顯示郵件內容,要求一個處理過的mail做引數


def showmessage(mail):


    if mail.is_multipart():


        for part in mail.get_payload():


            showmessage(part)


    else:


        type=mail.get_content_charset()


        if type==None:


            print mail.get_payload()


        else:


            try:


                print unicode(mail.get_payload(decode='base64'),type)


            except UnicodeDecodeError:


                print mail






#顯示全部郵件主題要求整個處理過的郵件列表


def showindex(processmaillist):


    count=1


    for mail in processmaillist:


        print count,


        showsubject(mail)


        print 


        if count%5==0:


            raw_input("\n[Press Enter key]")


        count+=1






#輸入傳送時需要的伺服器名等相關資訊,返回一個元組


def sendconfig():


    SMTPname=raw_input('SMTPserverName?')


    SMTPuser=raw_input('SMTPusername?')


    SMTPpass=raw_input('SMTPServerPassword?')


    To=raw_input('To?')


    From=raw_input('From?')


    return SMTPname,SMTPuser,SMTPpass,to,From






#輸入接收郵件時需要的相關輸入,返回一個元組


def popconfig():


   POPname=raw_input('POPServerName?')


   POPuser=raw_input('POPusername?')


   POPpass=raw_input('POPpassword?')


   return POPname,POPuser,POPpass






if __name__=='__main__':


    list=loadmail()


    maillist=processmail(list)


    interact(maillist)


    


















 nfqx 回覆於:2005-07-12 09:09:12


鼓勵一下


 bleem1998 回覆於:2005-07-12 09:20:57


very good


 xichen 回覆於:2005-07-12 09:23:46


對於漢字編碼的轉換,可以這樣做,我試過效果不錯。 
a='中國' 
a.decode('gb2312').encode("utf-8")


 guotie 回覆於:2006-06-27 11:17:03


very good ! 


thanks!


 kai0200 回覆於:2006-06-27 22:13:30


鼓勵一下 
幫你頂一下,向你學習!


[ 本帖最後由 kai0200 於 2006-7-4 22:22 編輯 ]


 guotie 回覆於:2006-08-28 14:58:22


請教一個問題,如何判斷郵件的附件? 


btw,樓主的showmessage()用walk()來實現似乎更好一點。


 guotie 回覆於:2006-08-29 14:56:54


判斷某個part的編碼應該使用Content-Transfer-Encoding。


 guotie 回覆於:2006-08-29 16:27:48


處理郵件內容時,這樣做更好一些: 
引數mail是通過message_from_string()得到的instance 


def mail_content(mail): 
    content =    '' 
    for part in mail.walk(): 
        if part.is_multipart(): 
            continue 


        ch =    part.get_content_charset() 
        if ch: 
            content +=    unicode(part.get_payload(decode = True),ch).encode('utf-8') 
        else: 
            content +=    part.get_payload(decode = True).decode('gb2312').encode('utf-8') 


    return content