發送一封電子郵件的過程:
發件人 -> MUA -> MTA -> MTA -> 若干個MTA - 【MDA】 <- MUA <- 收件人
有了上述基本概念,要編寫程序來發送和接收郵件,本質上就是:html
發郵件時,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支持有smtplib和email兩個模塊,email負責構造郵件,smtplib負責發送郵件。
首先咱們先構造純文本的郵件:(網易郵箱 —>qq郵箱)markdown
發送普通郵件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()
收取郵件就是編寫一個MUA做爲客戶端,從MDA把郵件獲取到用戶的電腦或者手機上。收取郵件最經常使用的協議是POP協議,目前版本號是3,俗稱POP3。Python內置一個poplib模塊,實現了POP3協議,能夠直接用來收郵件。
POP3協議收取的不是一個已經能夠閱讀的郵件自己,而是郵件的原始文本,這和SMTP協議很像,SMTP發送的也是通過編碼後的一大段文本。
要把POP3收取的文本變成能夠閱讀的郵件,還須要用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()
解析郵件
#導入必要模塊 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()