一、生成电子邮件

      电子邮件消息不仅包含纯文本,还有附件、文本中的格式等,这种较长的消息由多个部分组成。比如消息中由纯文本的部分,可能还有对应的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!\r\n', '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 = '\r\n\r\n'.join(('\r\n'.join(headers), '\r\n'.join(body)))

      首先,在实际的开发环境中,需要加密WEB上的连接,所以使用三个协议的SSL等价版本。其次,不能在代码中使用纯文本保存登录名和密码,这些信息要从安全的数据库、编译的字节码文件(.pyc或.pyo文件)、公司内联网中的服务器代理中获取。
      在这里邮件消息使用列表替换字符串,是因为在实际中,电子邮件消息正文是由应用生成或控制的,而不是硬编码的字符串。当邮件已经准备发送时,只需使用\r\n对调用str.join()就可以组装成正文(\r\n是兼容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 = '\r\n\r\n'.join(('\r\n'.join(headers), '\r\n'.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')

 

posted on 2018-10-31 14:53  Q同码  阅读(426)  评论(0编辑  收藏  举报