更多內容,請訪問個人 我的博客。html
先來了解一下收/發郵件有哪些協議:python
Python內置對SMTP/POP3/IMAP的支持。更多詳情請移步 Python官方教程編程
Python對SMTP支持有 smtplib
和 email
兩個模塊,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
,郵件正文是一個 str
,as_string()
把MIMEText對象變成 str
。服務器
注意: QQ郵件等須要手動開通 SMTP服務
, 郵箱設置 => 帳號 => POP3/SMTP服務,以下圖:測試
此時,咱們就能夠收到郵件了,以下:網站
郵件主題、顯示發件人、收件人等信息並非經過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()
複製代碼
收到的郵件,以下:
收件人並非咱們設置的 「收件人暱稱」,是由於不少郵件服務商在顯示郵件時,會把收件人名字自動替換爲用戶註冊的名字,這無傷大雅。
# 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郵件很簡單,在構造 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)
複製代碼
因爲 mac
自帶的郵件會自動把圖片附件插入郵件正文中,因此樣式很好看。可是普通郵件可能沒這麼便捷(抱歉,我沒見過普通郵件。。。小小得瑟一下)
若是要把一個圖片嵌入到郵件正文中怎麼作?直接在HTML郵件中連接圖片地址行不行?答案是,大部分郵件服務商都會自動屏蔽帶有外鏈的圖片,由於不知道這些連接是否指向惡意網站。
要把圖片嵌入到郵件正文中,咱們只需按照發送附件的方式,先把郵件做爲附件添加進去,而後,在HTML中經過引用 src="cid:0"
就能夠把附件做爲圖片嵌入了。若是有多個圖片,給它們依次編號,而後引用不一樣的 cid:x
便可。
把上面代碼加入 MIMEMultipart
的 MIMEText
從 plain
改成 html
,而後在適當的位置引用圖片,以下:
msg.attach(MIMEText('<html><body><h1>Hello</h1>' +
'<p><img src="cid:0"></p>' +
'</body></html>', 'html', 'utf-8'))
複製代碼
若是咱們發送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對象...
複製代碼
使用標準的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()
方法,就建立了安全鏈接。後面的代碼和前面的發送郵件代碼徹底同樣。
Python內置一個 poplib
模塊,實現了POP3協議,能夠直接用來收郵件。
注意到POP3協議收取的不是一個已經能夠閱讀的郵件自己,而是郵件的原始文本,這和SMTP協議很像,SMTP發送的也是通過編碼後的一大段文本。
要把POP3收取的文本變成能夠閱讀的郵件,還須要用email模塊提供的各類類來解析原始文本,變成可閱讀的郵件對象。
因此,收取郵件分兩步:
第一步:用 poplib
把郵件的原始文本下載到本地;
第二部:用 email
解析原始文本,還原爲郵件對象。
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: 測試內容
#
# ...
複製代碼
更多編程教學請關注公衆號:潘高陪你學編程