Python学习笔记(二十)收发电子邮件

参考资料:

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
今天就学习到这里,下一节从数据库访问开始学习。

猜你喜欢

转载自blog.csdn.net/alvin_2005/article/details/80575884
今日推荐