参考资料:
https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868325601402299d1e941914a21990ac7861ef4bc2d000
https://blog.csdn.net/Mark_LQ/article/details/51204081?locationNum=3&fps=1
1、电子邮件在网络上传输过程:发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
(1)MUA:Mail User Agent——邮件用户代理。客户端用于发送或接收邮件的软件,如Outlook、手机端的各类邮件管理APP等。
(2)MTA:Mail Transfer Agent——邮件传输代理。Email服务提供商,如新浪、搜狐、网易等。
(3)MDA:Mail Delivery Agent——邮件投递代理。邮件最终目标邮箱服务器。
2、收发电子邮件的程序是MUA的功能,使用SMTP(Simple Mail Transfer Protocol)协议发送邮件到MTA,使用POP(Post Office Protocol)协议或IMAP(Internet Message Access Protocol)协议从MDA收取邮件信息。IMAP协议不但能接收收件箱邮件,还可以操作邮箱其他目录,但POP协议只能从收件箱接收邮件。下面是我的学习代码:
发送邮件:
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import parseaddr, formataddr
from email.mime.base import MIMEBase
import smtplib
import os
#邮件正文类型格式常量-HTML
MAILTYPE_HTML = 'html'
#邮件正文类型格式常量-文本
MAILTYPE_TEXT = 'plain'
#对复合邮件地址(发件人名称<邮箱地址>)进行编码
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))
#用于发送邮件的类
class EmailSender(object):
def __init__(self):
#邮件标题
self.mTitle = None
#发件人地址
self.mFrom = None
#收件人地址
self.mTo = None
#发件人名称
self.mFromName = None
#收件人名称
self.mToName = None
#正文
self.mText = None
#正文类型,默认为文本
self.mType = MAILTYPE_TEXT
#编码格式
self.mCode = 'utf-8'
#附件列表
self.attachs = None
#判断附件类型,0-未知 1-图片 2-其他
def getAttachType(self, fname):
result = 0
try:
ext = os.path.splitext(fname)
sExt = None
if ext != None and len(ext) == 2:
sExt = ext[1].lower()
if sExt == '.png' or sExt == '.bmp' or sExt == '.jpeg' or sExt == '.jpg':
result = 1
else:
result = 2
except BaseException, e:
result = 0
return result
#添加附件到消息,0-添加失败,1-添加成功
def addAttach(self, fname, msg, id):
if msg == None or not isinstance(msg, MIMEMultipart):
return 0
#附件类型-->aType
aType = self.getAttachType(fname)
if aType > 0:
#文件名-->sFilename
sFilename = os.path.split(fname)[1]
#扩展名-->sExt
sExt = sFilename.split('.')[1]
with open(fname, 'rb') as f:
mime = None
#根据文件类型拼装附件对象-->mime
if aType == 1:
mime = MIMEBase('image', sExt, filename=sFilename)
if mime != None:
# 加上必要的头信息:
mime.add_header('Content-Disposition', 'attachment', filename=sFilename)
mime.add_header('Content-ID', '<%s>' % id)
mime.add_header('X-Attachment-Id', id)
# 把附件的内容读进来:
mime.set_payload(f.read())
# 用Base64编码:
encoders.encode_base64(mime)
# 添加到MIMEMultipart:
msg.attach(mime)
return 1
return 0
#发送邮件,smtp_server-服务器 passwd-密码
def send(self, smtp_server, passwd):
msg = None
s = None
#消息类型
if self.mType == MAILTYPE_HTML:
s = MIMEText(self.mText, 'html', self.mCode)
else:
s = MIMEText(self.mText, 'plain', self.mCode)
#如果没有附件则直接处理消息,否则添加附件
if self.attachs == None or not isinstance(self.attachs, list) or len(self.attachs) == 0:
msg = s
else:
msg = MIMEMultipart()
msg.attach(s)
id = 0
for fname in self.attachs:
if self.addAttach(fname, msg, '%d' % id) == 1:
id = id + 1
#消息发送地址
msg['From'] = _format_addr(('%s' if self.mFromName == None else self.mFromName + '<%s>') % self.mFrom)
#消息目标地址
msg['To'] = _format_addr(('%s' if self.mToName == None else self.mToName + '<%s>') % self.mTo)
#消息主题
msg['Subject'] = Header(self.mTitle, self.mCode).encode()
#发送
server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(self.mFrom, passwd)
server.sendmail(self.mFrom, [self.mTo], msg.as_string())
server.quit()
#字符串编码
def encode(str, code):
h = Header(str, code)
return h.encode()
#测试入口
def Test():
sender = EmailSender()
sServer = raw_input('Server:')
sPass = raw_input('Password:')
sender.mFrom = raw_input('From:')
sender.mFromName = raw_input('From name:')
sender.mTo = raw_input('To:')
sender.ToName = raw_input('To name:')
sender.mTitle = encode(raw_input('Title:'), "utf-8")
q = raw_input('mail type? 1-html, default - text')
if q == '1':
sender.mType = MAILTYPE_HTML
else:
sender.mType = MAILTYPE_TEXT
sender.mText = raw_input('Text:')
sender.attachs = []
while True:
s = raw_input('attach filename:')
if s == '':
break
else:
sender.attachs.append(s)
sender.send(sServer, sPass)
注:由于个人只有免费邮箱,先后尝试了网易、新浪、搜狐等SMTP服务,均未成功,最后使用阿里云的邮箱服务测试通过。
接收邮件:
import poplib, base64 from email.parser import Parser from email.utils import parseaddr from email.header import decode_header from email.header import Header #用于接收最新消息的方法 def Pop3Receive(server, addr, passwd): result = None popserver = poplib.POP3(server) popserver.user(addr) popserver.pass_(passwd) resp, mails, octets = popserver.list() index = len(mails) if index >= 1: resp, lines, octets = popserver.retr(index) sContent = '\r\n'.join(lines) result = Parser().parsestr(sContent) popserver.quit() return result #用于解码字符串 def decode_str(s): value, charset = decode_header(s)[0] if charset: value = value.decode(charset) pre = '=?'+charset + '?' if value.startswith(pre) and value.endswith('?='): v = value.split('?') if v and len(v) > 2: value = v[len(v) - 2] return value #用于识别消息编码格式 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 #用于解析邮件的类 class Mail(object): def __init__(self, msg, attpath): #标题 self.title = None #发件人名称 self.fromName = None #发件人地址 self.fromAddr = None #收件人名称 self.toName = None #收件人地址 self.toAddr = None #附件列表 self.attachs = [] #正文列表 self.contents = [] #错误信息 self.errors = [] #附件存放位置 self.attpath = attpath if msg != None: for header in ['From', 'To', 'Subject']: value = msg.get(header, '') if value: if header=='Subject': self.title = decode_str(value) else: # 需要解码Email地址: hdr, addr = parseaddr(value) name = decode_str(hdr) if header == 'From': self.fromName = name self.fromAddr = addr else: self.toName = name self.toAddr = addr self.parseContent(msg) #解析邮件正文(包括附件) def parseContent(self, msg): if (msg.is_multipart()): #递归解析 parts = msg.get_payload() for n, part in enumerate(parts): self.parseContent(part) 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) self.contents.append(content) else: # 不是文本,作为附件处理 # 具体代码参考自参考资料2 #解析附件文件名 fname = msg.get_filename() if fname: #解码文件名 h = Header(fname) dh = decode_header(h) fname = dh[0][0] if dh[0][1]: # 如果包含编码的格式,则按照该格式解码 fname = unicode(fname, dh[0][1]) fname = fname.encode("utf-8") #保存附件 try: data = msg.get_payload(decode=True) with open(self.attpath + fname, 'wb') as att_file: self.attachs.append(fname) att_file.write(data) except BaseException, e: self.errors.append('error on save %s:%s' % (fname, e.message)) #测试入口 def Test(): #输入pop3服务器地址 server = raw_input('POP3 Server:') #输入邮箱地址 addr = raw_input('Username:') #输入密码 passwd = raw_input('Password:') #接收最新邮件-->msg msg = Pop3Receive(server, addr, passwd) if msg != None: #输入附件存放位置 attpath = raw_input('path to save attachs:') if not attpath: attpath = '' else: if attpath != '' and not attpath.endswith('\\'): attpath = attpath + '\\' #解析邮件 mail = Mail(msg, attpath) if mail: print 'title:', mail.title print 'from:%s<%s>' % (mail.fromName, mail.fromAddr) print 'to:%s<%s>' % (mail.toName, mail.toAddr) print 'contents:', mail.contents print 'attachs:', mail.attachs print 'errors:', mail.errors今天就学习到这里,下一节从数据库访问开始学习。