Python使用POP3和SMTP協議收發郵件

前言

更多內容,請訪問個人 我的博客html


先來了解一下收/發郵件有哪些協議:python

  • SMTP協議
    SMTP(Simple Mail Transfer Protocol),即簡單郵件傳輸協議。至關於中轉站,將郵件發送到客戶端。
  • POP3協議
    POP3(Post Office Protocol 3),即郵局協議的第3個版本,是電子郵件的第一個離線協議標準。該協議把郵件下載到本地計算機,不與服務器同步,缺點是更易丟失郵件或屢次下載相同的郵件。
  • IMAP協議
    IMAP(Internet Mail Access Protocol),即交互式郵件存取協議。該協議鏈接遠程郵箱直接操做,與服務器內容同步。
  • Exchange服務
    Exchange服務是一個設計完備的郵件服務器產品,提供了一般所須要的所有郵件服務功能。除了常規SMTP/POP協議服務以外,它還支持IMAP4 、LDAP和NNTP協議。

Python內置對SMTP/POP3/IMAP的支持。更多詳情請移步 Python官方教程編程


SMTP發送郵件

Python對SMTP支持有 smtplibemail 兩個模塊,email 負責構造郵件,smtplib 負責發送郵件。瀏覽器

構造郵件

構造最簡單的純文本郵件,以下:安全

from email.mime.text import MIMEText

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
複製代碼

注意到構造 MIMEText 對象時,第一個參數就是郵件正文,第二個參數是MIME的subtype,傳入 'plain' 表示純文本,最終的MIME就是 'text/plain',最後必定要用 utf-8 編碼保證多語言兼容性。bash

發送郵件

import smtplib

# 輸入Email地址和口令:
from_addr = 'test_from_addr@qq.com'
password = 'Password'
# 輸入收件人地址:
to_addr = 'test_to_addr@qq.com'
# 輸入SMTP服務器地址:
smtp_server = smtp.qq.com

server = smtplib.SMTP(smtp_server, 25) # SMTP協議默認端口是25
# server.starttls() # 若是是SSL,則用 587 端口,再加上這句代碼就好了
server.set_debuglevel(1)    # 打印出和SMTP服務器交互的全部信息
server.login(from_addr, password)   # 登陸SMTP服務器
server.sendmail(from_addr, [to_addr], msg.as_string())    # 發郵件
server.quit()
複製代碼

sendmail() 方法就是發郵件,因爲能夠一次發給多我的,因此傳入一個 list,郵件正文是一個 stras_string() 把MIMEText對象變成 str服務器

注意: QQ郵件等須要手動開通 SMTP服務 , 郵箱設置 => 帳號 => POP3/SMTP服務,以下圖:測試

image

image

此時,咱們就能夠收到郵件了,以下:網站

image

添加郵件標題、收/發件人

郵件主題、顯示發件人、收件人等信息並非經過SMTP協議發送的,而是包含在 MIMEText 對象中,以下:ui

from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr

import smtplib

def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))

from_addr = 'test_from_addr@qq.com'
password = 'Password'
to_addr = 'test_to_addr@qq.com'
smtp_server = smtp.qq.com

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
msg['From'] = _format_addr('發件人暱稱 <%s>' % from_addr)
msg['To'] = _format_addr('收件人暱稱 <%s>' % to_addr)
msg['Subject'] = Header('這是個有主題的郵件', 'utf-8').encode()

server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
複製代碼

收到的郵件,以下:

image

收件人並非咱們設置的 「收件人暱稱」,是由於不少郵件服務商在顯示郵件時,會把收件人名字自動替換爲用戶註冊的名字,這無傷大雅。

添加抄送,暗送

# ccto_list 抄送
# bccto_list 暗送

msg['from'] = user
msg['to'] = ','.join(to_list)   #注意,不是分號
msg['cc'] = ','.join(ccto_list)
msg['subject'] = subject
server.sendmail(sender,to_list+ccto_list+bccto_list, str(msg))
複製代碼

發送HTML郵件

要發送HTML郵件很簡單,在構造 MIMEText 對象時,把HTML字符串傳進去,再把第二個參數由 plain 變爲 html ,以下:

msg = MIMEText('<html><body><h1>Hello</h1>' +
    '<p>send by <a href="http://blog.pangao.vip">PanGao’s blog</a>...</p>' +
    '</body></html>', 'html', 'utf-8')
複製代碼

發送附件

要想發送附件,須要構造一個 MIMEMultipart 對象表明郵件自己,而後往裏面加上一個 MIMEText 做爲郵件正文,再繼續往裏面加上表示附件的 MIMEBase 對象,以下:

from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase

# 郵件對象:
msg = MIMEMultipart()
msg['From'] = _format_addr('發件人暱稱 <%s>' % from_addr)
msg['To'] = _format_addr('收件人暱稱 <%s>' % to_addr)
msg['Subject'] = Header('這是個有主題的郵件', 'utf-8').encode()

# 郵件正文是MIMEText:
msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))

# 添加附件就是加上一個MIMEBase,從本地讀取一個圖片:
with open('/Users/pangao/Downloads/test.png', 'rb') as f:
    # 設置附件的MIME和文件名,這裏是png類型:
    mime = MIMEBase('image', 'png', filename='test.png')
    # 加上必要的頭信息:
    mime.add_header('Content-Disposition', 'attachment', filename='test.png')
    mime.add_header('Content-ID', '<0>')
    mime.add_header('X-Attachment-Id', '0')
    # 把附件的內容讀進來:
    mime.set_payload(f.read())
    # 用Base64編碼:
    encoders.encode_base64(mime)
    # 添加到MIMEMultipart:
    msg.attach(mime)
複製代碼

image

發送圖片

因爲 mac 自帶的郵件會自動把圖片附件插入郵件正文中,因此樣式很好看。可是普通郵件可能沒這麼便捷(抱歉,我沒見過普通郵件。。。小小得瑟一下)

若是要把一個圖片嵌入到郵件正文中怎麼作?直接在HTML郵件中連接圖片地址行不行?答案是,大部分郵件服務商都會自動屏蔽帶有外鏈的圖片,由於不知道這些連接是否指向惡意網站。

要把圖片嵌入到郵件正文中,咱們只需按照發送附件的方式,先把郵件做爲附件添加進去,而後,在HTML中經過引用 src="cid:0" 就能夠把附件做爲圖片嵌入了。若是有多個圖片,給它們依次編號,而後引用不一樣的 cid:x 便可。

把上面代碼加入 MIMEMultipartMIMETextplain 改成 html,而後在適當的位置引用圖片,以下:

msg.attach(MIMEText('<html><body><h1>Hello</h1>' +
    '<p><img src="cid:0"></p>' +
    '</body></html>', 'html', 'utf-8'))
複製代碼

同時支持HTML和Plain格式

若是咱們發送HTML郵件,收件人經過瀏覽器或者Outlook之類的軟件是能夠正常瀏覽郵件內容的,可是,若是收件人使用的設備太古老,查看不了HTML郵件怎麼辦?

辦法是在發送HTML的同時再附加一個純文本,若是收件人沒法查看HTML格式的郵件,就能夠自動降級查看純文本郵件。

利用 MIMEMultipart 就能夠組合一個HTML和Plain,要注意指定subtype是 alternative,以下:

msg = MIMEMultipart('alternative')
msg['From'] = ...
msg['To'] = ...
msg['Subject'] = ...

msg.attach(MIMEText('hello', 'plain', 'utf-8'))
msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8'))
# 正常發送msg對象...
複製代碼

加密SMTP

使用標準的25端口鏈接SMTP服務器時,使用的是明文傳輸,發送郵件的整個過程可能會被竊聽。要更安全地發送郵件,能夠加密SMTP會話,實際上就是先建立SSL安全鏈接,而後再使用SMTP協議發送郵件。

某些郵件服務商,例如Gmail,提供的SMTP服務必需要加密傳輸。咱們來看看如何經過Gmail提供的安全SMTP發送郵件。

必須知道,Gmail的SMTP端口是587,所以,修改代碼以下:

smtp_server = 'smtp.gmail.com'
smtp_port = 587
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
# 剩下的代碼和前面的如出一轍:
server.set_debuglevel(1)
...
複製代碼

只須要在建立 SMTP 對象後,馬上調用 starttls() 方法,就建立了安全鏈接。後面的代碼和前面的發送郵件代碼徹底同樣。

POP3收取郵件

Python內置一個 poplib 模塊,實現了POP3協議,能夠直接用來收郵件。

注意到POP3協議收取的不是一個已經能夠閱讀的郵件自己,而是郵件的原始文本,這和SMTP協議很像,SMTP發送的也是通過編碼後的一大段文本。

要把POP3收取的文本變成能夠閱讀的郵件,還須要用email模塊提供的各類類來解析原始文本,變成可閱讀的郵件對象。

因此,收取郵件分兩步:

第一步:用 poplib 把郵件的原始文本下載到本地;

第二部:用 email 解析原始文本,還原爲郵件對象。

經過POP3下載郵件

POP3協議自己很簡單,如下面的代碼爲例,咱們來獲取最新的一封郵件內容:

from email.parser import Parser
import poplib

# 輸入郵件地址, 口令和POP3服務器地址:
email = 'pangao1990@qq.com'
password = 'Password'
pop3_server = 'pop.qq.com'

# 鏈接到POP3服務器:
server = poplib.POP3_SSL(pop3_server)
# 能夠打開或關閉調試信息:
server.set_debuglevel(1)

# 身份認證:
server.user(email)
server.pass_(password)

# list()返回全部郵件的編號:
resp, mails, octets = server.list()

# 獲取最新一封郵件, 注意索引號從1開始:
index = len(mails)
resp, lines, octets = server.retr(index)

# lines存儲了郵件的原始文本的每一行,
# 能夠得到整個郵件的原始文本:
msg_content = b'\r\n'.join(lines).decode('utf-8')
# 稍後解析出郵件:
msg = Parser().parsestr(msg_content)

# 能夠根據郵件索引號直接從服務器刪除郵件:
# server.dele(index)
# 關閉鏈接:
server.quit()
複製代碼

可是這個 Message 對象自己多是一個 MIMEMultipart 對象,即包含嵌套的其餘 MIMEBase 對象,嵌套可能還不止一層。

因此咱們要遞歸地打印出 Message 對象的層次結構:

from email.header import decode_header
from email.utils import parseaddr


def print_info(msg, indent=0):
    if indent == 0:
        for header in ['From', 'To', 'Subject']:
            value = msg.get(header, '')
            if value:
                if header == 'Subject':
                    value = decode_str(value)
                else:
                    hdr, addr = parseaddr(value)
                    name = decode_str(hdr)
                    value = u'%s <%s>' % (name, addr)
            print('%s%s: %s' % (' ' * indent, header, value))
    if (msg.is_multipart()):
        parts = msg.get_payload()
        for n, part in enumerate(parts):
            print('%spart %s' % (' ' * indent, n))
            print('%s--------------------' % (' ' * indent))
            print_info(part, indent + 1)
    else:
        content_type = msg.get_content_type()
        if content_type == 'text/plain' or content_type == 'text/html':
            content = msg.get_payload(decode=True)
            charset = guess_charset(msg)
            if charset:
                content = content.decode(charset)
            print('%sText: %s' % (' ' * indent, content + '...'))
        else:
            print('%sAttachment: %s' % (' ' * indent, content_type))


def decode_str(s):
    value, charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value


def guess_charset(msg):
    charset = msg.get_charset()
    if charset is None:
        content_type = msg.get('Content-Type', '').lower()
        pos = content_type.find('charset=')
        if pos >= 0:
            charset = content_type[pos + 8:].strip()
    return charset


print_info(msg) #解析

# From: 木葉 <pangao1990@qq.com>
# To: <mail@pangao.vip>
# Subject: 測試主題
# Text: 測試內容
# 
# ...
複製代碼

更多編程教學請關注公衆號:潘高陪你學編程

image
相關文章
相關標籤/搜索