• 【python3的进阶之路二】因特网客户端编程 实战


    一、生成电子邮件

          电子邮件消息不仅包含纯文本,还有附件、文本中的格式等,这种较长的消息由多个部分组成。比如消息中由纯文本的部分,可能还有对应的HTML部分,这部分针对使用web浏览器作为邮件客户端的情形,除此之外还有一个或多个附件。邮件互换消息扩展(Mail Interchange Message Extension, MIME)格式就用来识别这些不同的部分。

    from email.mime.image import MIMEImage
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from smtplib import SMTP
    
    # multipart alternative:test and html
    def make_mpa_msg():
        email = MIMEMultipart('alternative')   # 创建一个带附件的实例
        text = MIMEText('Hello World!
    ', 'plain')   # 可包含三个参数,参数一文本内容,参数二plain设置文本格式,参数三UTF-8设置编码
        email.attach(text)
        html = MIMEText(
            '<html><body><h4>Hello World!</h4>'
            '</body></html>', 'html')
        email.attach(html)         # 添加到邮件正文
        return email
    
    # multipart:images
    def make_img_msg(fn):
        f = open(fn, 'rb')
        data = f.read()
        f.close()
        email = MIMEImage(data, name = fn)
        email.add_header("Content-Disposition",
                         "attachment; filename = '%s'" % fn)
        return email
    
    def sendMsg(fr, to, msg):
        s = SMTP('smtp.exmail.qq.com')
        mail_user = 'xxx@xxx.com'  # 用户名
        mail_pass = 'xxxx'  # 密码
        s.login(mail_user, mail_pass)
        errs = s.sendmail(fr, to, msg)
        s.quit()
    
    if __name__ == '__main__':
        SENDER = 'xxx@xxx.com'
        RECIPS = ['xxx@xxx.com']
        SOME_IMG_FILE = 'image.PNG'
        print('Sending multipart alternative msg...')
        msg = make_mpa_msg()
        msg['From'] = SENDER
        msg['To'] = ', '.join(RECIPS)
        msg['Subject'] = 'multipart alternative test'
        sendMsg(SENDER, RECIPS, msg.as_string())
        print('Sending image msg...')
        msg = make_img_msg(SOME_IMG_FILE)
        msg['From'] = SENDER
        msg['To'] = ', '.join(RECIPS)
        msg['Subject'] = 'image file test'
        sendMsg(SENDER, RECIPS, msg.as_string())

    MimeMulipart的三种子类型:mixed、alternative、related MIME—multipart类型

    二、解析电子邮件

    解析电子邮件一般用到email包中几个方法

    def processMsg(entire_mag):
        body = ''
        msg = email.message_from_string(entire_mag)         # 用来解析消息
        if msg.is_multipart(): # 如果邮件对象是一个MIMEMultipart
            for part in msg.walk():                # 遍历消息的附件
                if part.get_content_type() == 'text/plain':     # 获取正确MIME类型
                    body = part.get_payload()                   # get_payload()返回list,包含所有的子对象
                    # 从消息正文中获取特定的部分。通常decode标记设为True,即邮件正文根据每个Content-Transfer-Encoding头解码
                    break
                else:
                    body = msg.get_payload(decode=True)
        else:
            body = msg.get_payload(decode=True)
        return body

    三、最佳实践:安全、重构

    from smtplib import SMTP_SSL
    from poplib import POP3_SSL
    from imaplib import IMAP4_SSL
    
    from secret import *  # where MAILBOX , PASSWORD come from
    
    who = ''  # xxx@yahoo/gmail.com where MAILBOX = xxx
    from_ = who
    to = [who]
    
    headers = [
        'From: %s' % from_,
        'To: %s' % ', '.join(to),
        'Subject: test SMTP send via 465/SSL',
    ]
    
    body = [
        'Hello',
        'World!',
    ]
    
    msg = '
    
    '.join(('
    '.join(headers), '
    '.join(body)))

          首先,在实际的开发环境中,需要加密WEB上的连接,所以使用三个协议的SSL等价版本。其次,不能在代码中使用纯文本保存登录名和密码,这些信息要从安全的数据库、编译的字节码文件(.pyc或.pyo文件)、公司内联网中的服务器代理中获取。
          在这里邮件消息使用列表替换字符串,是因为在实际中,电子邮件消息正文是由应用生成或控制的,而不是硬编码的字符串。当邮件已经准备发送时,只需使用 对调用str.join()就可以组装成正文( 是兼容RFC5322的SMTP的服务器使用的正式分隔符,其他有些服务器至接受换行符)
          邮件的收件人可能不止一个,所以to也是列表形式,在创建最终的电子邮件头时需要使用str.join()将收件人连接到一起。

    def getSubject(msg, default = '(no Subject line)'):
        '''
        getSubject(msg) = 'msg' is an iterable, not a
        delimited single string; this function iterates
        over 'msg' look for Subject: line and returns
        if found, else the default is returned if one isn`t
        found in the headers 
        :param msg: 
        :param default: 
        :return: 
        '''
        for line in msg:
            if line.startswith('Subject:'):
                return line.rstrip()
            if not line:
                return default

          查看在Yahoo!Mail和Gmail实例中会用到一个特殊功能函数,该函数仅仅获取入站电子邮件消息的Subject行。getSubject()只查找邮件标题中的Subject行。如果发现一个该函数就立即返回;如果遇到空行表示邮件标题已结束,则返回一个默认值。
          从性能考虑有些人或使用line[:8] == 'Subject’来避免调用 str.startswith()方法,虽然line[:8] == 'Subject’会调用str.getslice(),但这种方法比str.startswith()快40%。

    四、Yahoo!Mail!

          该例子,需要一个Yahoo!Mail Plus账号。POP无法无法获取发送的邮件,但IMAP可以找到相应的邮件。

    s = SMTP_SSL('smtp.mail.yahoo.com', 465)
    s.login(MAILBOX, PASSWORD)
    s.sendmail(from_, to, msg)
    s.quit()
    print('SSL: mail sent!')
    
    s = POP3_SSL('pop.mail.yahoo.com', 995)
    s.user(MAILBOX)
    s.pass_(PASSWORD)
    rv, msg ,sz = s.retr(s.stat()[0])
    s.quit()
    line = getSubject(msg)
    print('POP:', line)
    
    s = IMAP4_SSL('imap.n.mail.yahoo.com', 993)
    s.login(MAILBOX, PASSWORD)
    rsp, msgs = s.select('INBOX', True)
    rsp, data = s.fetch(msgs[0], '(RFC822)')
    line = getSubject(StringIO(data[0][1]))
    s.close()
    s.quit()
    print('IMAP:', line)

    yahoo邮箱已无法注册,以下代码无法调试

    from imaplib import  IMAP4_SSL
    from platform import python_version
    from poplib import POP3_SSL, error_proto
    from socket import error
    from io import StringIO
    # SMTP_SSL added in 2.6, fixed in 2.6.3
    release = python_version()
    if release > '2.6.2':
        from smtplib import SMTP_SSL, SMTPServerDisconnected
    else:
        SMTP_SSL = None
    
    from secret import *  # you provide MAILBOX, PASSWORD
    
    who = '%s@yahoo.com' % MAILBOX
    from_ = who
    to = [who]
    
    headers = [
        'From: %s' % from_,
        'To: %s' ', '.join(to),
        'Subject: test SMTP send via 465/SSL',
    ]
    body = [
        'Hello',
        'world!',
    ]
    msg = '
    
    '.join(('
    '.join(headers), '
    '.join(body)))
    
    def getSubject(msg, default = '(no Subject line)'):
        '''
        getSubject(msg) - iterate over 'msg' looking for
        Subject line ; return if found otherwise 'default'
        '''
        for line in msg:
            if line.startswith('Subject:'):
                return line.rstrip()
            if not line:
                return default
    
    #SMTP/SSL
    print('*** Doing SMTP send via SSL...')
    if SMTP_SSL:
        try:
            s = SMTP_SSL('smtp.mail.yahoo.com', 465)
            s.login(MAILBOX, PASSWORD)
            s.sendmail(from_, to, msg)
            s.quit()
            print('SSL mail sent!')
        except SMTPServerDisconnected:
            print('error: server unexpectedly disconnected...try again')
    else:
        print('error: SMTP_SSL requires 2.6.3+')
    
    # POP
    print('***Doing POP recv...')
    try:
        s = POP3_SSL('pop.mail.yahoo.com', 995)
        s.user(MAILBOX)
        s.pass_(PASSWORD)
        rv, msg, sz = s.retr(s.stat()[0])
        s.quit()
        line = getSubject(msg)
        print('Received msg via POP: %r' % line)
    except error_proto:
        print('error: POP for Yahoo!Mail Plus subscribers only')
    
    # IMAP
    print('***Doing IMAP recv...')
    try:
        s = IMAP4_SSL('imap.n.mail.yahoo.com', 993)
        s.login(MAILBOX, PASSWORD)
        rsp, msgs = s.select('INBOX', True)
        rsp, data = s.fetch(msgs[0], '(RFC822)')
        line = getSubject(StringIO(data[0][1]))
        s.close()
        s.logout()
        print('Received msg via IMAP: %r' % line)
    except error:
        print('error: IMAP for Yahoo!Mail Plus subscribers only')
  • 相关阅读:
    [UVA10859 放置街灯 Placing Lampposts]
    洛谷7月月赛题解(2020)
    [学习笔记]马拉车-Manacher
    [SP1026] FAVDICE
    [NOIP2013]货车运输
    [洛谷P1801]黑匣子
    [HAOI2015]树上染色
    python-第二块:time模块和datatime模块
    python-作业:员工信息表
    python-第二块,笔记整理和学习内容复习(day7)
  • 原文地址:https://www.cnblogs.com/CSgarcia/p/9883121.html
Copyright © 2020-2023  润新知