Python使用poplib模組和smtplib模組收發電子郵件的教程,poplibsmtplib
poplib模組內送郵件
python的poplib模組是用來從pop3收取郵件的,也可以說它是處理郵件的第一步。
POP3協議並不複雜,它也是採用的一問一答式的方式,你向伺服器發送一個命令,伺服器必然會回複一個資訊。pop3命令碼如下:
命令 poplib方法 參數 狀態 描述-----------------------------------------------------------------------------------------------USER user username 認可 使用者名稱,此命令與下面的pass命令若成功,將導致狀態轉換PASS pass_ password 認可 使用者密碼 APOP apop Name,Digest 認可 Digest是MD5訊息摘要-----------------------------------------------------------------------------------------------STAT stat None 處理 請求伺服器發回關於郵箱的統計資料,如郵件總數和總位元組數UIDL uidl [Msg#] 處理 返回郵件的唯一識別碼,POP3會話的每個標識符都將是唯一的LIST list [Msg#] 處理 返回郵件數量和每個郵件的大小RETR retr [Msg#] 處理 返回由參數標識的郵件的全部文本DELE dele [Msg#] 處理 伺服器將由參數標識的郵件標記為刪除,由quit命令執行RSET rset None 處理 伺服器將重設所有標記為刪除的郵件,用於撤消DELE命令TOP top [Msg#] 處理 伺服器將返回由參數標識的郵件前n行內容,n必須是正整數NOOP noop None 處理 伺服器返回一個肯定的響應----------------------------------------------------------------------------------------------QUIT quit None 更新
python的poplib也針對這些命令分別提供了對應的方法,上面在第二列裡已經標出來。收取郵件的過程一般是:
1. 串連pop3伺服器 (poplib.POP3.__init__)
2. 發送使用者名稱和密碼進行驗證 (poplib.POP3.user poplib.POP3.pass_)
3. 擷取郵箱中信件資訊 (poplib.POP3.stat)
4. 收取郵件 (poplib.POP3.retr)
5. 刪除郵件 (poplib.POP3.dele)
6. 退出 (poplib.POP3.quit)
注意的是,上面我在括弧裡寫的是使用什麼方法來完成這個操作,在實際的代碼中不能那樣寫,應該是建立poplib.POP3的對象,然後,調用這個對象的方法。比如:
poplib.POP3.quit
應該理解為
a = poplib.POP3(host)a.quit()
下面看看實際的代碼:
#-*- encoding: gb2312 -*-import os, sys, stringimport poplib# pop3伺服器位址host = "pop3.163.com"# 使用者名稱username = "xxxxxx@163.com"# 密碼password = "xxxxxxx"# 建立一個pop3對象,這個時候實際上已經串連上伺服器了pp = poplib.POP3(host)# 設定偵錯模式,可以看到與伺服器的互動資訊pp.set_debuglevel(1)# 向伺服器發送使用者名稱pp.user(username)# 向伺服器發送密碼pp.pass_(password)# 擷取伺服器上信件資訊,返回是一個列表,第一項是一共有多上封郵件,第二項是共有多少位元組ret = pp.stat()print ret# 需要取出所有信件的頭部,信件id是從1開始的。for i in range(1, ret[0]+1): # 取出信件頭部。注意:top指定的行數是以信件頭為基數的,也就是說當取0行, # 其實是返回頭部資訊,取1行其實是返回頭部資訊之外再多1行。 mlist = pp.top(i, 0) print 'line: ', len(mlist[1])# 列出伺服器上郵件資訊,這個會對每一封郵件都輸出id和大小。不象stat輸出的是總的統計資訊ret = pp.list()print ret# 取第一封郵件完整資訊,在傳回值裡,是按行儲存在down[1]的列表裡的。down[0]是返回的狀態資訊down = pp.retr(1)print 'lines:', len(down)# 輸出郵件for line in down[1]: print line# 退出pp.quit()
在有些地方,有安全郵件這一說,其實是對pop3做了ssl加密。這樣的,poplib一樣可以處理,只不過不是用POP3這個類,而是用POP3_SSL, 他們的方法都一樣。因此支援ssl在上面代碼中,替換建立pop3對象的一行為:
pp = poplib.POP3_SSL(host)
smtplib: 用python發送SSL/TLS安全郵件
python的smtplib提供了一種很方便的途徑寄送電子郵件。它對smtp協議進行了簡單的封裝。
smtp協議的基本命令包括:
- HELO 向伺服器標識使用者身份
- MAIL 初始化郵件傳輸 mail from:
- RCPT 標識單個的郵件接收人;常在MAIL命令後面,可有多個rcpt to:
- DATA 在單個或多個RCPT命令後,表示所有的郵件接收人已標識,並初始化資料轉送,以.結束
- VRFY 用於驗證指定的使用者/郵箱是否存在;由於安全方面的原因,伺服器常禁止此命令
- EXPN 驗證給定的郵箱列表是否存在,擴充郵箱列表,也常被禁用
- HELP 查詢服務器支援什麼命令
- NOOP 無操作,伺服器應響應OK
- QUIT 結束會話
- RSET 重設會話,當前傳輸被取消
- MAIL FROM 指定寄件者地址
- RCPT TO 指明的接收者地址
一般smtp會話有兩種方式,一種是郵件直接投遞,就是說,比如你要發郵件給zzz@163.com,那就直接連接163.com的郵件伺服器,把信投給zzz@163.com; 另一種是驗證過後的發信,它的過程是,比如你要發郵件給zzz@163.com,你不是直接投到163.com,而是通過自己在sina.com的另一個郵箱來發。這樣就要先串連sina.com的smtp伺服器,然後認證,之後在把要發到163.com的信件投到sina.com上,sina.com會幫你把信投遞到163.com。
第一種方式的命令流程基本是這樣:
1. helo
2. mail from
3. rcpt to
4. data
5. quit
但是第一種發送方式一般有限制的,就是rcpt to指定的這個郵件接收者必須在這個伺服器上存在,否則是不會接收的。 先看看代碼:
#-*- encoding: gb2312 -*-import os, sys, stringimport smtplib# 郵件伺服器地址mailserver = "smtp.163.com"# smtp會話過程中的mail from地址from_addr = "asfgysg@zxsdf.com"# smtp會話過程中的rcpt to地址to_addr = "zhaoweikid@163.com"# 信件內容msg = "test mail"svr = smtplib.SMTP(mailserver)# 設定為偵錯模式,就是在會話過程中會有輸出資訊svr.set_debuglevel(1)# helo命令,docmd方法包括了擷取對方伺服器返回資訊svr.docmd("HELO server")# mail from, 發送郵件寄件者svr.docmd("MAIL FROM: <%s>" % from_addr)# rcpt to, 郵件接收者svr.docmd("RCPT TO: <%s>" % to_addr)# data命令,開始發送資料svr.docmd("DATA")# 發送本文資料svr.send(msg)# 比如以 . 作為本文發送結束的標記,用send發送的,所以要用getreply擷取返回資訊svr.send(" . ")svr.getreply()# 發送結束,退出svr.quit()
注意的是,163.com是有反垃圾郵件功能的,想上面的這種投遞郵件的方法不一定能通過反垃圾郵件系統的檢測的。所以一般不推薦個人這樣發送。
第二種有點不一樣:
1.ehlo
2.auth login
3.mail from
4.rcpt to
5.data
6.quit
相對於第一種來說,多了一個認證過程,就是auth login這個過程。
#-*- encoding: gb2312 -*-import os, sys, stringimport smtplibimport base64# 郵件伺服器地址mailserver = "smtp.163.com"# 郵件使用者名username = "xxxxxx@163.com"# 密碼password = "xxxxxxx"# smtp會話過程中的mail from地址from_addr = "xxxxxx@163.com"# smtp會話過程中的rcpt to地址to_addr = "yyyyyy@163.com"# 信件內容msg = "my test mail"svr = smtplib.SMTP(mailserver)# 設定為偵錯模式,就是在會話過程中會有輸出資訊svr.set_debuglevel(1)# ehlo命令,docmd方法包括了擷取對方伺服器返回資訊svr.docmd("EHLO server")# auth login 命令svr.docmd("AUTH LOGIN")# 發送使用者名稱,是base64編碼過的,用send發送的,所以要用getreply擷取返回資訊svr.send(base64.encodestring(username))svr.getreply()# 發送密碼svr.send(base64.encodestring(password))svr.getreply()# mail from, 發送郵件寄件者svr.docmd("MAIL FROM: <%s>" % from_addr)# rcpt to, 郵件接收者svr.docmd("RCPT TO: <%s>" % to_addr)# data命令,開始發送資料svr.docmd("DATA")# 發送本文資料svr.send(msg)# 比如以 . 作為本文發送結束的標記svr.send(" . ")svr.getreply()# 發送結束,退出svr.quit()
上面說的是最普通的情況,但是不能忽略的是現在好多企業郵件是支援安全郵件的,就是通過SSL發送的郵件,這個怎麼發呢?SMTP對SSL安全郵件的支援有兩種方案,一種老的是專門開啟一個465連接埠來接收ssl郵件,另一種更新的做法是在標準的25連接埠的smtp上增加一個starttls的命令來支援。
看看第一種怎麼辦:
#-*- encoding: gb2312 -*-import os, sys, string, socketimport smtplibclass SMTP_SSL (smtplib.SMTP): def __init__(self, host='', port=465, local_hostname=None, key=None, cert=None): self.cert = cert self.key = key smtplib.SMTP.__init__(self, host, port, local_hostname) def connect(self, host='localhost', port=465): if not port and (host.find(':') == host.rfind(':')): i = host.rfind(':') if i >= 0: host, port = host[:i], host[i+1:] try: port = int(port) except ValueError: raise socket.error, "nonnumeric port" if not port: port = 654 if self.debuglevel > 0: print>>stderr, 'connect:', (host, port) msg = "getaddrinfo returns an empty list" self.sock = None for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: self.sock = socket.socket(af, socktype, proto) if self.debuglevel > 0: print>>stderr, 'connect:', (host, port) self.sock.connect(sa) # 新增加的建立ssl串連 sslobj = socket.ssl(self.sock, self.key, self.cert) except socket.error, msg: if self.debuglevel > 0: print>>stderr, 'connect fail:', (host, port) if self.sock: self.sock.close() self.sock = None continue break if not self.sock: raise socket.error, msg # 設定ssl self.sock = smtplib.SSLFakeSocket(self.sock, sslobj) self.file = smtplib.SSLFakeFile(sslobj); (code, msg) = self.getreply() if self.debuglevel > 0: print>>stderr, "connect:", msg return (code, msg) if __name__ == '__main__': smtp = SMTP_SSL('192.168.2.10') smtp.set_debuglevel(1) smtp.sendmail("zzz@xxx.com", "zhaowei@zhaowei.com", "xxxxxxxxxxxxxxxxx") smtp.quit()
這裡我是從原來的smtplib.SMTP派生出了新的SMTP_SSL類,它專門來處理ssl串連。我這裡測試的192.168.2.10是我自己的測試伺服器.
第二種是新增加了starttls的命令,這個很簡單,smtplib裡就有這個方法,叫smtplib.starttls()。當然,不是所有的郵件系統都支援安全郵件的,這個需要從ehlo的傳回值裡來確認,如果裡面有starttls,才表示支援。相對於發送普通郵件的第二種方法來說,只需要新增加一行代碼就可以了:
#-*- encoding: gb2312 -*-import os, sys, stringimport smtplibimport base64# 郵件伺服器地址mailserver = "smtp.163.com"# 郵件使用者名username = "xxxxxx@163.com"# 密碼password = "xxxxxxx"# smtp會話過程中的mail from地址from_addr = "xxxxxx@163.com"# smtp會話過程中的rcpt to地址to_addr = "yyyyyy@163.com"# 信件內容msg = "my test mail"svr = smtplib.SMTP(mailserver)# 設定為偵錯模式,就是在會話過程中會有輸出資訊svr.set_debuglevel(1)# ehlo命令,docmd方法包括了擷取對方伺服器返回資訊,如果支援安全郵件,傳回值裡會有starttls提示svr.docmd("EHLO server")svr.starttls() # <------ 這行就是新加的支援安全郵件的代碼!# auth login 命令svr.docmd("AUTH LOGIN")# 發送使用者名稱,是base64編碼過的,用send發送的,所以要用getreply擷取返回資訊svr.send(base64.encodestring(username))svr.getreply()# 發送密碼svr.send(base64.encodestring(password))svr.getreply()# mail from, 發送郵件寄件者svr.docmd("MAIL FROM: <%s>" % from_addr)# rcpt to, 郵件接收者svr.docmd("RCPT TO: <%s>" % to_addr)# data命令,開始發送資料svr.docmd("DATA")# 發送本文資料svr.send(msg)# 比如以 . 作為本文發送結束的標記svr.send(" . ")svr.getreply()# 發送結束,退出svr.quit()
注意: 以上的代碼為了方便我都沒有判斷傳回值,嚴格說來,是應該判斷一下返回的代碼的,在smtp協議中,只有傳回碼是2xx或者3xx才能繼續下一步,返回4xx或5xx的,都是出錯了。