Python中使用SMTP發送郵件以及POP收取郵件

  1. 假設咱們本身的電子郵件地址是from@163.com,對方的電子郵件地址是to@sina.com(這裏的地址虛擬的),如今咱們用Outlook或者Foxmail之類的軟件寫好郵件,填上對方的Email地址,點「發送」,電子郵件就發出去了。這些電子郵件軟件被稱爲MUA:Mail User Agent——郵件用戶代理。
  2. Email從MUA發出去,不是直接到達對方電腦,而是發到MTA:Mail Transfer Agent——郵件傳輸代理,就是那些Email服務提供商,好比網易、新浪等等。因爲咱們本身的電子郵件是163.com,因此,Email首先被投遞到網易提供的MTA,再由網易的MTA發到對方服務商,也就是新浪的MTA。這個過程當中間可能還會通過別的MTA。
  3. Email到達新浪的MTA後,因爲對方使用的是@sina.com的郵箱,所以,新浪的MTA會把Email投遞到郵件的最終目的地MDA:Mail Delivery Agent——郵件投遞代理。Email到達MDA後,就會保存在新浪的某個服務器上,存放在某個文件或特殊的數據庫裏,咱們將這個長期保存郵件的地方稱之爲電子郵箱。對方要取到郵件,必須經過MUA從MDA上把郵件取到本身的電腦上。

發送一封電子郵件的過程:
  發件人 -> MUA -> MTA -> MTA -> 若干個MTA - 【MDA】 <- MUA <- 收件人
  有了上述基本概念,要編寫程序來發送和接收郵件,本質上就是:html

  1. 編寫MUA把郵件發到MTA;
  2. 編寫MUA從MDA上收郵件。

發郵件時,MUA和MTA使用的協議就是SMTP:Simple Mail Transfer Protocol,後面的MTA到另外一個MTA也是用SMTP協議。
收郵件時,MUA和MDA使用的協議有兩種:POP:Post Office Protocol,目前版本是3,俗稱POP3;IMAP:Internet Message Access Protocol,目前版本是4,優勢是不但能取郵件,還能夠直接操做MDA上存儲的郵件,好比從收件箱移到垃圾箱,等等。數據庫

  郵件客戶端軟件在發郵件時,會讓你先配置SMTP服務器,也就是你要發到哪一個MTA上。假設你正在使用163的郵箱,你就不能直接發到新浪的MTA上,由於它只服務新浪的用戶,因此,你得填163提供的SMTP服務器地址:smtp.163.com,爲了證實你是163的用戶,SMTP服務器還要求你填寫郵箱地址和郵箱口令,這樣,MUA才能正常地把Email經過SMTP協議發送到MTA。相似的,從MDA收郵件時,MDA服務器也要求驗證你的郵箱口令,確保不會有人冒充你收取你的郵件,因此,Outlook之類的郵件客戶端會要求你填寫POP3或IMAP服務器地址、郵箱地址和口令,這樣,MUA才能順利地經過POP或IMAP協議從MDA取到郵件。服務器

 

發送郵件

  SMTP是發送郵件的協議,Python內置對SMTP的支持,能夠發送純文本郵件、HTML郵件以及帶附件的郵件。Python對SMTP支持有smtplibemail兩個模塊,email負責構造郵件,smtplib負責發送郵件。
首先咱們先構造純文本的郵件:(網易郵箱 —>qq郵箱)markdown

  • 參數1:郵件正文(hello,world)
  • 參數2:MIME的subtype,傳入‘plain’,最終的MIME就是’text/plain’
  • 參數3:表明編碼

發送普通郵件ide

# 輸入Email地址和口令:
from_addr = input('From: ')
#這裏的密碼必定是受權碼,163郵箱原始密碼不行。
password = input('Password: ')
# 輸入SMTP服務器地址:這裏咱們用smtp.163.com
smtp_server = input('SMTP server: ')
# 輸入收件人地址:
to_addr = input('To: ')

#用來格式化郵件地址
from email.header import Header
from email.utils import parseaddr, formataddr

def _format_addr(s):
    name, addr = parseaddr(s)#這個函數會解析出姓名和郵箱地址
    return formataddr(( \
        Header(name, 'utf-8').encode(), \
        addr.encode('utf-8') if isinstance(addr, unicode) else addr))
#設置發件人,收件人姓名和郵件主題
msg['From'] = _format_addr(u'發件的人A <%s>' % from_addr)
msg['To'] = _format_addr(u'收件的人B <%s>' % to_addr)
msg['Subject'] = Header(u'測試郵件……', 'utf-8').encode()

import smtplib
server = smtplib.SMTP(smtp_server, 25) # SMTP協議默認端口是25
server.set_debuglevel(1)#打印出和SMTP服務器交互的全部信息
server.login(from_addr, password)#登陸服務器
#發送郵件,這裏第二個參數是個列表,能夠有多個收件人
#郵件正文是一個str,as_string()把MIMEText對象變成str
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

 

發送HTML郵件函數

#只須要修改這一行代碼,把正文換成html格式文本,plain換成html,其餘不變
msg = MIMEText('<html><body><h1>Hello</h1>' +
    '<p>send by <a href="http://blog.csdn.net/csdn15698845876">csdn</a>...</p>' +
    '</body></html>', 'html', 'utf-8')
#-*-encoding:utf-8 -*-
from email.mime.text import MIMEText
from email.header import Header
from email.utils import parseaddr, formataddr
import smtplib

from_addr = raw_input('From: ')
password = raw_input('Password: ')
smtp_server = raw_input('SMTP server: ')
to_addr = raw_input('To: ')

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

msg = MIMEText('<html><body><h1>Hello</h1>' +
    '<p>send by <a href="http://blog.csdn.net/csdn15698845876">csdn</a>...</p>' +
    '</body></html>', 'html', 'utf-8')
msg['From'] = _format_addr(u'張康 <%s>' % from_addr)
msg['To'] = _format_addr(u'朋友 <%s>' % to_addr)
msg['Subject'] = Header(u'HTML郵件', '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()
不變部分

 

發送帶附件的郵件post

  帶附件的郵件能夠看作包含若干部分的郵件:文本和各個附件自己,因此,能夠構造一個MIMEMultipart對象表明郵件自己,而後往裏面加上一個MIMEText做爲郵件正文,再繼續往裏面加上表示附件的MIMEBase對象便可。測試

#-*-encoding:utf-8 -*-
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.utils import parseaddr, formataddr

import smtplib

from_addr = raw_input('From: ')
password = raw_input('Password: ')
to_addr = raw_input('To: ')
smtp_server = raw_input('SMTP server: ')

# 郵件對象:
def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr(( \
        Header(name, 'utf-8').encode(), \
        addr.encode('utf-8') if isinstance(addr, unicode) else addr))

msg = MIMEMultipart()
msg['From'] = _format_addr(u'發件的人A <%s>' % from_addr)
msg['To'] = _format_addr(u'收件的人B <%s>' % to_addr)
msg['Subject'] = Header(u'帶附件的郵件', 'utf-8').encode()

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

# 添加附件就是加上一個MIMEBase,從本地讀取一個圖片:
with open('1.jpg', 'rb') as f:
    # 設置附件的MIME和文件名,這裏是jpg類型:
    mime = MIMEBase('image', 'jpg', filename='1.jpg')
    # 加上必要的頭信息:
    mime.add_header('Content-Disposition', 'attachment', filename='1.jpg')
    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)

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()

 

示例:從QQ郵箱發送到網易郵箱 
  因爲從qq郵箱到網易郵箱須要SSL協議,因此代碼有一點變化,並且QQ郵箱須要開啓IMAP/SMTP服務,登陸密碼須要使用受權碼。ui

  開啓IMAP/SMTP服務服務流程:qq郵箱——設置——帳戶
這裏寫圖片描述
  這裏已經開啓了,沒有開啓的點擊開啓,而後按照流程去操做會獲得一個受權碼。編碼

# -*- encoding: utf-8 -*-
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import smtplib

from_addr=raw_input('From:')
password=raw_input('Password:')
to_addr=raw_input('To:')
smtp_server=raw_input('SMTP server:')#這裏是smtp.qq.com

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

msg=MIMEText('hello,send by qq mail','plain','utf-8')
msg['From'] = _format_addr(u'qq郵箱 <%s>' % from_addr)
msg['To'] = _format_addr(u'網易郵箱 <%s>' % to_addr)
msg['Subject'] = Header(u'發件人A……', 'utf-8').encode()

server=smtplib.SMTP_SSL(smtp_server, 465)#SSL協議端口465
server.set_debuglevel(1)
server.login(from_addr,password)
server.sendmail(from_addr,[to_addr],msg.as_string())
server.quit()
QQ郵箱發送到163郵箱

 

收取郵件

  收取郵件就是編寫一個MUA做爲客戶端,從MDA把郵件獲取到用戶的電腦或者手機上。收取郵件最經常使用的協議是POP協議,目前版本號是3,俗稱POP3。Python內置一個poplib模塊,實現了POP3協議,能夠直接用來收郵件。

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

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

  • 用poplib把郵件的原始文本下載到本地;
  • 用email解析原始文本,還原爲郵件對象。

經過POP3下載郵件

import poplib

# 輸入郵件地址, 口令和POP3服務器地址:
email = raw_input('Email: ')
password = raw_input('Password: ')
pop3_server = raw_input('POP3 server: ')

# 鏈接到POP3服務器:例如pop.163.com
server = poplib.POP3(pop3_server)
# 能夠打開或關閉調試信息:
# server.set_debuglevel(1)
# 可選:打印POP3服務器的歡迎文字:
#print(server.getwelcome())

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

# stat()返回郵件數量和佔用空間:
print('Messages: %s. Size: %s' % server.stat())
# list()返回全部郵件的編號:
resp, mails, octets = server.list()
# 能夠查看返回的列表相似['1 82923', '2 2184', ...]
print(mails)
# 獲取最新一封郵件, 注意索引號從1開始:
index = len(mails)
resp, lines, octets = server.retr(index)
# lines存儲了郵件的原始文本的每一行,它是個列表

# 能夠得到整個郵件的原始文本:
msg_content = '\r\n'.join(lines)
# 解析出郵件:這裏輸出msg是個亂的,尚未真正的解析
msg = Parser().parsestr(msg_content)
# 能夠根據郵件索引號直接從服務器刪除郵件:
# server.dele(index)
# 關閉鏈接:
server.quit()
經過POP3下載郵件

 

解析郵件

#導入必要模塊
import email
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr

#只須要一行代碼就能夠把郵件內容解析爲Message對象:
msg = Parser().parsestr(msg_content)

"""
可是這個Message對象自己多是一個MIMEMultipart對象,即包含嵌套的其餘MIMEBase對象,嵌套可能還不止一層。因此咱們要遞歸地打印出Message對象的層次結構
"""

# indent用於縮進顯示:
def print_info(msg, indent=0):
    if indent == 0:
        # 郵件的From, To, Subject存在於根對象上:
        for header in ['From', 'To', 'Subject']:
            value = msg.get(header, '')
            if value:
                if header=='Subject':
                    # 須要解碼Subject字符串:
                    value = decode_str(value)
                else:
                    # 須要解碼Email地址:
                    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()):
        # 若是郵件對象是一個MIMEMultipart,
        # get_payload()返回list,包含全部的子對象:
        parts = msg.get_payload()
        for n, part in enumerate(parts):
            print('%spart %s' % ('  ' * indent, n))
            print('%s--------------------' % ('  ' * indent))
            # 遞歸打印每個子對象:
            print_info(part, indent + 1)
    else:
        # 郵件對象不是一個MIMEMultipart,
        # 就根據content_type判斷:
        content_type = msg.get_content_type()
        if content_type=='text/plain' or content_type=='text/html':
            # 純文本或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))

#郵件的Subject或者Email中包含的名字都是通過編碼後的str,要正常顯示,就必須decode

def decode_str(s):
    value, charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value
"""
decode_header()返回一個list,由於像Cc、Bcc這樣的字段可能包含多個郵件地址,因此解析出來的會有多個元素。
上面的代碼咱們偷了個懶,只取了第一個元素。文本郵件的內容也是str,還須要檢測編碼,不然,非UTF-8編碼的郵件都沒法正常顯示
"""

def guess_charset(msg):
    # 先從msg對象獲取編碼:
    charset = msg.get_charset()
    if charset is None:
        # 若是獲取不到,再從Content-Type字段獲取:
        content_type = msg.get('Content-Type', '').lower()
        pos = content_type.find('charset=')
        if pos >= 0:
            charset = content_type[pos + 8:].strip()
    return charset
解析郵件

 

上述過程完整代碼以下:

# -*- coding: utf-8 -*-

import poplib
import email
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr

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

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

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))

email = raw_input('Email: ')
password = raw_input('Password: ')
pop3_server = raw_input('POP3 server: ')

server = poplib.POP3(pop3_server)
#server.set_debuglevel(1)
print(server.getwelcome())
# 認證:
server.user(email)
server.pass_(password)
print('Messages: %s. Size: %s' % server.stat())
resp, mails, octets = server.list()
# 獲取最新一封郵件, 注意索引號從1開始:
resp, lines, octets = server.retr(len(mails))
# 解析郵件:
msg = Parser().parsestr('\r\n'.join(lines))
# 打印郵件內容:
print_info(msg)
# 慎重:將直接從服務器刪除郵件:
# server.dele(len(mails))
# 關閉鏈接:
server.quit()
完整代碼
相關文章
相關標籤/搜索