zoukankan      html  css  js  c++  java
  • Python中使用SMTP发送邮件以及POP收取邮件

    1. 假设我们自己的电子邮件地址是from@163.com,对方的电子邮件地址是to@sina.com(这里的地址虚拟的),现在我们用Outlook或者Foxmail之类的软件写好邮件,填上对方的Email地址,点“发送”,电子邮件就发出去了。这些电子邮件软件被称为MUA:Mail User Agent——邮件用户代理。
    2. Email从MUA发出去,不是直接到达对方电脑,而是发到MTA:Mail Transfer Agent——邮件传输代理,就是那些Email服务提供商,比如网易、新浪等等。由于我们自己的电子邮件是163.com,所以,Email首先被投递到网易提供的MTA,再由网易的MTA发到对方服务商,也就是新浪的MTA。这个过程中间可能还会经过别的MTA。
    3. Email到达新浪的MTA后,由于对方使用的是@sina.com的邮箱,因此,新浪的MTA会把Email投递到邮件的最终目的地MDA:Mail Delivery Agent——邮件投递代理。Email到达MDA后,就会保存在新浪的某个服务器上,存放在某个文件或特殊的数据库里,我们将这个长期保存邮件的地方称之为电子邮箱。对方要取到邮件,必须通过MUA从MDA上把邮件取到自己的电脑上。

    发送一封电子邮件的过程:
    发件人 -> MUA -> MTA -> MTA -> 若干个MTA - 【MDA】 <- MUA <- 收件人
    有了上述基本概念,要编写程序来发送和接收邮件,本质上就是:

    1. 编写MUA把邮件发到MTA;
    2. 编写MUA从MDA上收邮件。

    发邮件时,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邮箱)

    参数1:邮件正文(hello,world)
    参数2:MIME的subtype,传入‘plain’,最终的MIME就是’text/plain’
    参数3:代表编码

    from email.mime.text import MIMEText
    msg = MIMEText('hello, world', 'plain', 'utf-8')
    # 输入Email地址和口令:
    from_addr = raw_input('From: ')
    #这里的密码一定是授权码,163邮箱原始密码不行。
    password = raw_input('Password: ')
    # 输入SMTP服务器地址:这里我们用smtp.163.com
    smtp_server = raw_input('SMTP server: ')
    # 输入收件人地址:
    to_addr = raw_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'张康 <%s>' % from_addr)
    msg['To'] = _format_addr(u'朋友 <%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()

    发件箱截图

    这里写图片描述

    收件箱截图

    这里写图片描述

    发送带附件的邮件

    带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个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'张康 <%s>' % from_addr)
    msg['To'] = _format_addr(u'朋友 <%s>' % to_addr)
    msg['Subject'] = Header(u'带附件的邮件', 'utf-8').encode()
    
    # 邮件正文是MIMEText:
    msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))
    
    # 添加附件就是加上一个MIMEBase,从本地读取一个图片:
    with open('E:\pycode2017-10\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服务,登录密码需要使用授权码。

    开启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'来自张康的问候……', '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模块提供的各种类来解析原始文本,变成可阅读的邮件对象。
    所以,收取邮件分两步:

    • :用poplib把邮件的原始文本下载到本地;
    • :用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 = '
    '.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('
    '.join(lines))
    # 打印邮件内容:
    print_info(msg)
    # 慎重:将直接从服务器删除邮件:
    # server.dele(len(mails))
    # 关闭连接:
    server.quit()
    

    以我自己的邮箱为例,最新一封邮件是:
    这里写图片描述

    程序输出:

    这里写图片描述

  • 相关阅读:
    java8 stream().map().collect()用法
    Java中Collections的emptyList、EMPTY_LIST详解
    zxing实现java二维码生成和解析
    机器学习与数据挖掘—K邻近算法(KNN)
    第一个Web项目(IDEA)
    Ucore操作系统实验-实验课程设计
    数据挖掘与机器学习--损失函数
    机器数据挖掘--常见监督学习算法以及数据挖掘流程
    Tomcat安装-环境变量配置-启动-关闭
    操作系统实验教程(Ucore)--Lab6
  • 原文地址:https://www.cnblogs.com/neuzk/p/9476426.html
Copyright © 2011-2022 走看看