python核心編程筆記——因特網客戶端編程(三)

ps:python版本爲3.6.2

電子郵件

. 根據RFC2822的定義,“(電子郵件)消息由頭字段(統稱消息標題)以及後面可選的正文組成”。即郵件可以沒有正文,但是一定要有標題。

電子郵件系統組件和協議

. 實際上電子郵件誕生於因特網之前,電子郵件最早是使用在同一臺主機上的不同用戶之間的通信,網絡出現後,用戶就可以在不同的主機之間進行通信。
  電子郵件是如何工作呢?簡單來說,有一臺發送計算機(發件人的消息從這裏發送出去)和一臺接收計算機(收件人的郵件服務器)。最好的解決方案是發送計算機知道如何連接到接收計算機,這樣就可以直接把消息發送過去。但實際上一般沒有這麼順利。
  發送計算機需要找到某一臺中間主機,而這臺中間主機最終能到達最後的接收主機。接着這臺中間主機需要找到一臺離接收主機更近一些的主機。所以,在發送主機和接收主機之間,可能會有多臺稱爲跳板的主機。如果仔細看看收到的電子郵件消息頭標題,會看到一個“護照”標記,其中記錄了這封郵件最終抵達之前,一路上都到過哪些地方。
  電子郵件系統最重要的組件是消息傳輸代理(MTA),這是在郵件交換主機上運行的服務器進程,它負責郵件的路由、隊列處理和發送工作。MTA 就是郵件從發送主機到接收主機所要經過的主機和“跳板”,所以也稱爲“消息傳輸”的“代理”。
  要讓所有這些工作起來,MTA 要知道兩件事情:1)如何找到消息應該到達的下一臺MTA;2)如何與另一臺 MTA 通信。第一件事有域名服務(DNS)來查找目的域名的MX(MaileXchange,郵件交換)來完成,查找到的可能不是最終主機,而是一個跳板。

發送電子郵件

. 爲了發送電子郵件,郵件客戶端必須要連接到一個 MTA,MTA 靠某種協議進行通信。MTA之間通過消息傳輸系統(MTS)互相通信。只有兩個 MTA 都使用這個協議時,才能進行通信。由於以前存在很多不同的計算機系統,每個系統都使用不同的網絡軟件,因此這種通信很危險,具有不可預知性。更復雜的是,有的計算機使用互連的網絡,而有的計算機使用調制解調器撥號,消息的發送時間也是不可預知的。這種複雜性導致了現代電子郵件的基礎之一 —— 簡單郵件傳輸協議(Simple Mail Transfer Protocol,SMTP)的誕生。
  SMTP 是在因特網上的 MTA 之間消息交換的最常用 MTSMTA。用 SMTP 把電子郵件從一臺(MTA)主機傳送到另一臺(MTA)主機。 發電子郵件時,必須要連接到一個外部 SMTP服務器,此時郵件程序是一個 SMTP 客戶端。而 SMTP 服務器也因此成爲消息的第一站。

Python和SMTP

. Python也有一個smtplib模塊和一個需要實例化的smtplib.SMTP類,其通信過程如下:
  1.連接到服務器;
  2.登錄(根據需要);
  3.發出服務請求;
  4.退出。
  像NNTP一樣,登錄是可選的,只有在服務器啓用了SMTP身份驗證(SMTP-AUTH)時才需要登錄,其通信時也只要一個端口號:25.

smtplib.SMTP類方法

. 下表列舉一些常見的SMTP對象的方法:

方法 描述
sendmail(from, to, msg[, mopts, ropts]) 將 msg 從 from 發送至 to(以列表或元組表示),還可以選擇性地設置 ESMTP 郵件(mopts)和收件人(ropts)選項
ehlo()或 helo() 使用 EHLO 或 HELO 初始化 SMTP 或 ESMTP 服務器的會話。這是可選的,因爲sendmail()會在需要時自動調用相關內容
starttls(keyfile=None, certfile=None) 讓服務器啓用 TLS (安全傳輸層協議)模式。如果給定了 keyfile 或 certfile,則它們用來創建安全套接字
set_debuglevel(level) 爲服務器通信設置調試級別
quit() 關閉連接並退出
login(user, passwd) 使用用戶名和密碼登錄 SMTP 服務器

. 對大多數電子郵件發送程序來說,只需要兩個方法:sendmail()和 quit()。sendmail()的所有參數都要遵循 RFC 2822,即電子郵件地址必須要有正確的格式,消息正文要有正確的前導標題,正文必須由回車和換行符(\r\n)對分隔

交互式SMTP示例

. 介紹一個交互式示例,使用QQ郵箱的STMP服務器發送郵件:

>>> s = smtplib.SMTP('smtp.qq.com')
>>> s.login('[email protected]','xxxxxxxxxx')		#這裏輸入的不是密碼,而是授權碼
(235, b'Authentication successful')
>>> s.sendmail('[email protected]','[email protected]','\r\nxxxxxx\r\n')
{}

接受電子郵件

. 對於家庭用戶來說,在家裏放一個工作站來運行SMTP是不現實的,因此需要一種新的系統,能夠週期性的把郵件下載到本地,以供離線使用。
  這種家用的應用程序叫做郵件用戶代理(Mail User Agent,MUA)。MUA從服務器上下載郵件,在這個過程中可能會自動刪除郵件。不過MUA也需要能發送郵件,也就是說:在發送郵件的時候,應用程序能夠直接使用SMTP和MUA進行通信。

POP和IMAP

. 第一個用於下載郵件的協議是郵局協議(Post Office Protocal,POP),“郵局協議(POP)的目的是讓用戶的工作站可以訪問郵箱服務器裏的郵件,並在工作站中通過簡單郵件傳輸協議(SMTP)將郵件發送到郵件服務器”。
  在 POP 出現幾年之後有了一個與之競爭的協議,即因特網消息訪問協議(Internet Message Access Protocol,IMAP)。IMAP 旨在提供比 POP 更完整的解決方案,但它也因此比 POP 更復雜。當前廣泛使用的版本是 IMAP4rev1。實際上,Microsoft Exchange 這個當今最主要的郵件服務器使用 IMAP 作爲下載方式。

Python和POP3

. 與之前一樣,這裏導入 poplib 並實例化 poplib.POP3 類。標準流程如下所示:
  1.連接到服務器;
  2.登錄;
  3.發出服務請求;
  4.退出。
  其僞代碼如下:

from poplib import POP3
p = POP3('pop.python.is.cool')
p.user(...)
p.pass_(...)
...
p.quit()

交互式POP3示例

. 下面是使用 Python 的 poplib 模塊的交互式示例:

>>> import poplib
>>> p = poplib.POP3_SSL('pop.qq.com')
>>> p.user('[email protected]')
b'+OK'
>>> p.pass_('xxxxxxxxxxx')					#也是授權碼,不是密碼
b'+OK'
>>> p.stat()
(14, 180452)
>>> rsp,msg,siz = p.retr(14)
>>> rsp,siz
(b'+OK', 853)

poplib.POP3類方法

. 下表是常用的一些POP3對象的方法

方法 描述
user(loginname) 向服務器發送登錄名,並顯示服務器的響應,表示服務器正在等待輸入該用戶的密碼
pass_(passwd) 在用戶使用 user()登錄後,發送 passwd。如果登錄失敗,則拋出異常
stat() 返回郵件的狀態,即一個長度爲 2 的元組(msg_ct, mbox_siz),分別表示消息的數量和消息的總大小(即字節數)
list([msgnum]) stat()的擴展,從服務器返回以三元組表示的整個消息列表(rsp, msg_list, rsp_siz),分別爲服務器的響應、消息列表、返回消息的大小。如果給定了 msgnum,則只返回指定消息的數據
retr(msgnum) 從服務器中得到消息的 msgnum,並設置其“已讀”標誌。返回一個長度爲 3 的元組(rsp, msglines, msgsiz),分別爲服務器的響應、消息的 msgnum 的所有行、消息的字節數
dele(msgnum) 把消息 msgnum 標記爲刪除,大多數服務器在調用 quit()後執行刪除操作
quit() 註銷、提交修改(如處理“已讀”和“刪除”標記等)、解鎖郵箱、終止連接,然後退出

客戶端程序SMTP和POP3示例

. 以下程序用SMTP和POP3創建一個既能下載也能上傳和發送電子郵件的客戶端:

from smtplib import SMTP
from poplib import POP3_SSL
from time import sleep

Smtpsvr = 'smtp.qq.com'
Popsvr = 'pop.qq.com'

who = '[email protected]'
body = '''\
From: %(who)s
To: %(to)s
Subject: test msg

Hello World!
''' % {'who': who}

sendSvr = SMTP(Smtpsvr)
sendSvr.login('[email protected]','xxxxxxxxxxxx')
errs = sendSvr.sendmail(who,[who],body)
sendSvr.quit()
assert len(errs) == 0, errs
sleep(10)

recvSvr = POP3_SSL(Popsvr)
recvSvr.user('[email protected]')
recvSvr.pass_('xxxxxxxxxxxx')
rsp,msg,siz = recvSvr.retr(recvSvr.stat()[0])
for eachLine in msg:
    print(eachLine)

Python和IMAP4

. Python通過imaplib模塊支持IMAP4,流程與之前介紹的一樣,下面是僞代碼:

from imaplib import IMAP4
s = IMAP4('imap.python.is.cool')
s.login(...)
...
s.close()
s.logout()

imaplib.IMAP4 類中的常用方法

. IMAP 協議比 POP 複雜,下表只列出了一些基本方法:

方法 描述
close() 關閉當前郵箱。如果訪問權限不是隻讀,則本地刪除的郵件在服務器端也會被丟棄
fetch(message_set, message_parts) 獲取之前由message_set設置的電子郵件消息狀態(或使用message_parts獲取部分狀態信息)
login(user, password) 使用指定的用戶名和密碼登錄
logout() 從服務器註銷
noop() ping 服務器,但不產生任何行爲
search(charset, *criteria) 查詢郵箱中至少匹配一塊 criteria 的消息。如果 charset 爲 False,則默認使用 US-ASCII
select(mailbox= 'INBOX ', read-only=False) 選擇一個文件夾(默認是 INBOX),如果是隻讀,則不允許用戶修改其中的內容

交互式 IMAP4 示例

>>> from imaplib import IMAP4
>>> s = IMAP4('imap.qq.com')
>>> s.login('[email protected]','xxxxxxxxx')	#還是授權碼
('OK', [b'Success login ok'])
>>> rsp,msgs = s.select('INBOX',True)
>>> rsp
'OK'
>>> msgs
[b'169']
>>> rsp,data = s.fetch(msgs[0],'(RFC822)')
>>> rsp
'OK'
>>> for line in data[0][1].splitlines()[:5]:
	print(line)

	
b'X-QQ-mid: sysmail1t1547604076thzpz0bpw'
b'X-QQ-ACTION: no-bounce'
b'X-QQ-STYLE: 1'
b'X-QQ-INNER-PENDING: 1'
b'X-QQ-INNER-SUBJECT: =?GBK?B?zajTw8n6yNXM4dDR08q8/g==?='
>>> s.close()
('OK', [b'CLOSE Done'])
>>> s.logout()
('BYE', [b'LOGOUT received'])

實戰

生成電子郵件

. 電子郵件消息可能不僅包含了純文本,還有附件、文本中的格式等。郵件互換消息擴展(MIME)格式就用來識別這些不同的部分。
  Python的email包很適合處理並管理整個電子郵件消息的 MIME 部分,下面是兩個創建電子郵件的示例,即 make_mpa_msg()和 make_img_msg(),前者創建的電子郵件包含一條鏈接,後者創建的電子郵件包含一張圖片:

from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from smtplib import SMTP

def make_mpa_msg():
    email = MIMEMultipart('alternative')
    text = MIMEText('Hello World!','plain','utf-8')
    email.attach(text)
    html = MIMEText(
        '<p><a href="http://www.baidu.com">這是一個鏈接!</a></p>','html','utf-8')
    email.attach(html)
    return email

def make_img_msg(fn):
    f = open(fn,'rb')
    data = f.read()
    f.close()
    email = MIMEImage(data,name = fn)
    email.add_header('Content-Disposition',
                     'attachment;filename = "%s"' % fn)
    return email

def sendMsg(fr,to,msg):
    s = SMTP('smtp.qq.com')
    s.login('[email protected]','xxxxxxxx')
    errs = s.sendmail(fr,to,msg)
    s.quit()

if __name__ == '__main__':
    SENDER = 'xxxxxxx'
    RECIPS = 'xxxxxxx'
    img_name = 'x:/xx/xx'
    print('send multipart alternative msg...')
    msg = make_mpa_msg()
    msg['From'] = SENDER
    msg['To'] = ','.join(RECIPS)
    msg['Subject'] = 'multipart alternative test'
    sendMsg(SENDER,RECIPS,msg.as_string())

    print('sending image msg...')
    msg = make_img_msg(img_name)
    msg['From'] = SENDER
    msg['To'] = ','.join(RECIPS)
    msg['Subject'] = 'image file test'
    sendMsg(SENDER,RECIPS,msg.as_string())

. 創建多部分類型的消息需要使用到 email.mime.multiple.MIMEMultipart 類,並傳遞alternative作爲唯一的參數來實例化這個類。
  純文本和鏈接都會用到 email.mime.text.MIMEText 類。這兩部分是在郵件創建之後才創建的。
  創建帶圖片的消息需要使用到email.mime.image.MIMEImage 實例。
  (疑問:在IDLE中查看msg類容的時候能看到是多部分的,但從QQ郵箱中的郵件來看只顯示了鏈接而沒有顯示文本。)

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