参考资料:
https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868325601402299d1e941914a21990ac7861ef4bc2d000
https://blog.youkuaiyun.com/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
今天就学习到这里,下一节从数据库访问开始学习。