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