使用Python腳本來收發Gmail, Say no to GFW

博主的新站點,看此文章也可以來這裏。


學校裏面的網絡環境一直令人不快,天天喊着要建立國際一流大學,就校園網這質量只能是下輩子的事了,再加上國內目前對Google方面的不友好,我會經常性的不能正常通過瀏覽器連接到我的Gmail郵箱,捉急的時候真是很後悔當初選擇了Gmail,雖然它看起來又高端又時尚又有Geek風範。對Outlook和Mac上自帶Mail軟件的失望讓我有了這一想法,本文裏面說到這個版本還處於初級階段,不過應應急看郵件回覆緊急事情肯定是夠用了。


廢話不多說直接進入正題了,本文將要告訴你的事情是,如果你也想我一樣需要一個應急的接收Gmail的小插件,這篇文章可以幫助你在數分鐘之內搭建一個收發Gmail的小平臺,當然前提是你也和我一樣喜歡使用CLI的形式與你的計算機交互。


首先我們先來看看接收郵件的part


如果你想敲幾個命令就連上你的郵箱大致需要下面的幾個步驟:第一,你要與你的郵箱服務器建立一個成功的連接,當然我使用的是IMAP的協議,個人覺着比POP更好,當然人人都有自己的喜好,這也不是啥關鍵的事情。第二,在連接成功建立的基礎上你要使用自己的帳號和口令成功登陸,也就是去的認證獲取對該用戶郵箱的操作權。第三,在前兩者都OK的基礎上你可以開始搞一些事情了,例如獲取目前的郵箱一些基本情況(郵件總數,未讀數量,已讀數量,容量情況),在此基礎上你就可以讀取某一封郵件了(目前在帶有附件,HTML元素,圖片的郵件上還比較無力)。


雖然看起來功能弱爆了,但是對我來講已經足夠了,想象一下一個焦急等待面試結果或者等待女朋友是否同意求婚而苦守在郵箱門前卻因爲GFW的緣故無法得知結果的可憐人的痛苦心情吧!代碼的實現大概是這樣子的(只貼上關鍵部分,對於怎麼組織所有代碼是個太靈活的事情,誰讓咱們有那麼多模式呢)。


def __init__(self): 
    self.IMAP_SERVER='imap.gmail.com'
    self.IMAP_PORT=993
    self.M = None
    self.respons
    self.mailboxes = [] 
def login(self, username, password): 
    self.M = imaplib.IMAP4_SSL(self.IMAP_SERVER, self.IMAP_PORT) 
    rc, self.response = self.M.login(username, password) 
    return rc 

這部分就是第一步和第二步所做到事情。


def receive_mail(self):       
    recvMail = receiveMail.ReceiveMail(self.M) 
    mailCounts = recvMail.get_mail_count()
    print 'A total of '+ mailCounts +' mails in your input mailbox.'
    print 'A total of '+recvMail.get_unread_count()+ ' UNREAD mails in your input mailbox.'
    recvMail.get_imap_quota()
    mailBody = recvMail.check_simpleInfo(mailCounts) #返回值是郵件content
    if mailBody != 0:
        recvMail.check_detailInfo(mailBody)
    return

上面的這些代碼是出現在調用層的,主要是調用了具體實現功能的下面幾個函數。


下面的這些則在receiveMail.py文件中。


def check_simpleInfo(self, mailCounts):
    print "Input 'y' to check the lasted UNread mail. Other cmds to abandon!"
    while True:
        argv = string.split(raw_input('$====>'))
        if len(argv)!=0:
            if argv[0] == 'y':
                mailBody = self.get_mail_simpleInfo_from_id(mailCounts)
                return mailBody
            else:
                return 0
        else:
            pass
    else:
        pass

調用check_simpleInfo()函數查閱某郵件的簡略信息,包括郵件來自誰,發送時間,主題等。 調用check_detailInfo()函數來查看郵件的詳細信息也就是郵件內容。當然都看得出來get_mail_simpleInfo_from_id()纔是主要進行了獲取內容的工作,下面來看一下這個函數。


def get_mail_simpleInfo_from_id(self, id): 
    status, response = self.M.fetch(id,"(RFC822)")
    mailText = response[0][1]
    mail_message = email.message_from_string(mailText)
    subject = unicode(email.Header.make_header(email.Header.decode_header(mail_message['subject'])))
    mail_from = email.utils.parseaddr(mail_message["from"])[1]
    mail_to = email.utils.parseaddr(mail_message["to"])[1]
    print '['+mail_message['Date']+']'+'\n'+'From:'+mail_from+ ' To:'+mail_to+'\n'+'Subject:'+subject+'\n'
    return self.get_first_text_block(mail_message)

由於我們在工作中會使用到中文,所以在這裏我們還需要關注兩個有可能產生亂碼的地方,一個是郵件頭部分在代碼中使用了email.Header對其進行解碼,email.Header.decode_header(subject)對郵件主體可能出現的亂碼進行了處理。關於此處stackoverflow上有一熱心的哥們堅定的表示像以往那樣只是簡單的對他們進行解碼是不夠的,因爲此處的mail_message['subject']有可能回返回多個實體,而不僅僅是一個,所以爲了保證數據的完整採用了列表解析循環獲取數據,他強烈推薦我使用subject = u''.join(unicode(strs,t) for strs,t in email.Header.decode_header(mail_message['subject'])),從這句代碼可以看出python是多麼省事兒的語言。但是這裏還是不夠嚴密的,那就是有些郵件在發送的時候沒有指定明確的編碼格式,這就導致了在一定的機率下,email.Header.decode_header()會返回None作爲第二個返回值,會造成unicode()的失敗,於是此大神在報告bug的時候推薦給我了上面的那句代碼,subject = unicode(email.Header.make_header(email.Header.decode_header(mail_message['subject'])))

顯然get_first_text_block()函數對郵件的主要內容進行處理,用來判斷這個作爲參數傳進來的郵件實體的主體類型是哪個?如果還是multipart類型還需要繼續分解一下最後的目標就是將他們全部分解爲text類型。


def get_first_text_block(self,email_message_instance):
    maintype = email_message_instance.get_content_maintype()
    if maintype == 'multipart':
        for part in email_message_instance.get_payload():
            if part.get_content_maintype() == 'text':
                return part.get_payload(decode=True).strip()
    elif maintype == 'text':
        return email_message_instance.get_payload(decode=True).strip()

其中part.get_payload(decode=True)函數對解析的郵件主要內容進行了解碼,避免將一些亂七八糟的mojibake直接顯示在你的終端裏。說到這裏不能不再囉嗦一件事,雖然很不起眼但是卻很容易再這裏出問題,那就是你terminal的編碼格式,如果你的編碼格式不支持中文或者utf-8的畫python會報錯的,沒錯你沒有看錯,terminal的編碼錯誤會造成python的崩潰,我就遇到了此問題,雖說Mac的terminal是支持各種編碼的但是正巧我做這一部分的時候是在使用公司的Thinkpad,於是在window的CMD下就果斷悲劇了,直接報了python的編碼不能找到定義字符集(UnicodeDecodeError),所以此處是需要注意的。


下面的這個函數其實沒有什麼必要說的,將其單獨拿出來完全是爲了業務邏輯上的考慮,此函數中傳入的參數就是get_first_text_block()函數的返回值,在調用層的時候將其賦值給了mailBody,可以參見上面的receive_mail函數。


def check_detailInfo(self, mailBody):
    print "Input 'y' to check the detailInfo. Other cmds to abandon!"
    while True:
        args = string.split(raw_input('$====>'))
        if len(args)!=0:
            if args[0] == 'y':
                self.get_mail_content(mailBody)
                return
            else:
                break
        else:
            pass
    else:
        pass    

def get_mail_content(self, content):
    print 'MailContent:'+'\n'+content #直接將內容顯示到了終端

上面的代碼就是在接受郵件時用到的主要函數,當然還有其他的一些像修改、新增、刪除收件箱,按照發送者篩選查詢等功能就沒有貼上來了。


下面看看創建一個郵件併發送給一個收件列表的主要實現吧


首先我要告訴你的是,想要發一封郵件也需要三個步驟。第一,同樣你要成功的連接到郵箱的服務器(我使用的是SMTP協議,這個協議地球人都知道啦估計)。第二,同樣要使用自己的信息成功登陸取得權限,在這要多說一點,Gmail的安全驗證做的更爲完善,普通的SSL不能滿足它了,在你連通server之後必須要使用STARTTSL協議來將之前取得連接的SSL來升級爲更加安全的加密通道,只有這樣才能登陸成功,否則會提示你server的AUTH授權失敗。具體的原因有興趣的還可以參看這篇文章,老外都把事情講得很詳細。第三,此後你就可以暢快的發送郵件了。


def send_new_mail(self):
    send_to_str = raw_input("Input Email address that you want send your mail to,"+'\n'+\
    "if you have multiple address, seperate them with comma."+'\n'+\
    '$===>')
    send_list = send_to_str.split(',')
    send_subject  = raw_input('The Subject of new mail:')
    send_content  = raw_input('The Content of new mail:')
    sendMailInstance = sendMail.SendMail(send_list, send_subject, send_content)
    if sendMailInstance.send_mail():
        print 'New mail send success!'
    else:
        print 'New mail send failed!'
    return

同樣上面的代碼出現在調用層。實現出現在sendMail.py文件中。


def send_mail(self):  
    me="hello"+"<"+self.mail_user+"@"+self.mail_postfix+">"  
    msg = MIMEText(self.content,'plain','utf-8')  
    msg['Subject'] = Header(self.subject, 'utf-8')
    msg['From'] = me      
    msg['To'] = ";".join(self.send_list)  
    msg['Date'] = email.Utils.formatdate()
    try:  
        smtpServer = smtplib.SMTP()  
        smtpServer.connect(self.mail_host)  
        smtpServer.starttls()
        smtpServer.login(self.mail_user,self.mail_pass)  
        smtpServer.sendmail(me, self.send_list, msg.as_string())  
        smtpServer.close()  
        return True 
    except Exception, e:  
        print str(e)  
        return False  

代碼中smtpServer.starttls()此句就是我上提到過的升級安全加密策略的語句,剛知道僅用一句代碼就實現的時候還是被Python強大的庫驚呆了。其中,不加msg['Date'] = email.Utils.formatdate()這句的話發送出的郵件默認時間爲:UTC-07:00 休斯頓、底特律時間,加上之後恢復正常,此問題的原因我還沒有詳細追究。


從上面的代碼不難看出這是能夠單純的發送文本信息的情況,其實其他的像包含HTML和附件和圖片的代碼也不是很複雜,只是我覺着我現在暫時還用不到,也就沒有着急實現。


代碼的基本情況大致就是這樣了,當然沒有逐句的解釋這些代碼,我覺着邏輯簡單命名還算完整,其中有些庫函數大家感興趣的可以Google之,豐富的類庫和充足的資料的保證下總體感覺代碼開發工作量很小,只能說以後要是再有什麼需求就決定繼續用Python了,就一個字,方便!快捷!現在下面貼兩張我運行的效果圖。


選擇獲取最新的一封郵件:



選擇發送一封新的郵件:



你能相信一個菜鳥屌絲學生程序員在一段很短的時間內就從完全沒概念到完成編碼加debug麼?你以爲屌絲逆襲了,錯,是Python太Powerful了!當然還有更加Powerful的Google在幫我,雖然它時不時上不去還得在.hk .sg .com/ncr之間來回換着用,但Google的存在至少對程序員來說就是個太美好的存在了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章