python之email
来源:互联网 发布:红旗linux证书 编辑:程序博客网 时间:2024/05/28 06:06
如果现在我们要给朋友发一个邮件,假设我们自己的电子邮件地址是me@163.com
,对方的电子邮件地址是friend@sina.com
(注意地址都是虚构的哈),现在我们用Outlook
或者Foxmail
之类的软件写好邮件,填上对方的Email地址,点“发送”,电子邮件就发出去了。这些电子邮件软件被称为MUA:Mail User Agent——邮件用户代理。
Email从MUA发出去,不是直接到达对方电脑,而是发到MTA:Mail Transfer Agent——邮件传输代理,就是那些Email服务提供商,比如网易、新浪等等。由于我们自己的电子邮件是163.com
,所以,Email首先被投递到网易提供的MTA,再由网易的MTA发到对方服务商,也就是新浪的MTA。这个过程中间可能还会经过别的MTA,但是我们不关心具体路线,我们只关心速度。
Email到达新浪的MTA后,由于对方使用的是@sina.com
的邮箱,因此,新浪的MTA会把Email投递到邮件的最终目的地MDA:Mail Delivery Agent——邮件投递代理。Email到达MDA后,就静静地躺在新浪的某个服务器上,存放在某个文件或特殊的数据库里,我们将这个长期保存邮件的地方称之为电子邮箱。
同普通邮件类似,Email不会直接到达对方的电脑,因为对方电脑不一定开机,开机也不一定联网。对方要取到邮件,必须通过MUA从MDA上把邮件取到自己的电脑上。
所以,一封电子邮件的旅程就是:
发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
有了上述的基本概念,要编写程序来发送和接收邮件,本质上就是:
1.编写MUA把邮件发到MTA,编写MUA从MDA上收邮件。
发邮件的时候,MUA和MTA使用的协议就是SMTP:Simple Mail Transfer Protocol,后面的MTA到另一个MTA也是用SMTP协议。
收邮件的时候,MUA和MDA使用的协议有两种:POP: Post Office Protocol,目前版本是3。俗称POP3; IMAP: IInternet Message Access Protocol,目前版本是4,不但能取邮件,还可以直接操作MDA上存储的邮件,比如从收件箱移到垃圾箱等等。
邮件客户端软件在发送邮件时,会让你先配置SMTP服务器,也就是你要发送到哪个MTA上。如果你是163邮箱,你就得填163提供的SMTP服务器地址:smtp.163.com,为了证明你是163的用户,SMTP服务器还会要求你填写邮箱地址和邮箱口令。这样,MUA才能把email通过SMTP协议发送到MTA.
类似的,从MDA收邮件的时候,MDA服务器也要求验证你的邮箱口令,确保不会有人冒充你。
SMTP:SMTP是发送邮件的协议,python内置对SMTP的支持,可以发送纯文本邮件,HTML邮件以及带附件的邮件。
Python对SMTP的支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。
#-*-coding:utf-8-*-from email import encodersfrom email.header import Headerfrom email.mime.text import MIMETextfrom email.utils import parseaddr, formataddrimport smtplib#编写一个函数_format_addr()来格式化一个邮件地址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))from_addr = raw_input('From:')password = raw_input('Password:')to_addr = raw_input('To:')smtp_server = raw_input('SMTP server:')#注意到构造一个MIMEText对象时,第一个参数是邮件的正文,第二个参数是MIME的subtype.#subtype传入'plian',最终的MIME就是'text/plain',utf-8编码保证多语言的兼容性msg = MIMEText('hello, send by python...','plain','utf-8')msg['From'] = _format_addr(u'python爱好者 <%s>' % from_addr)msg['To'] = _format_addr(u'管理员 <%s>' % to_addr)msg['Subject'] = Header(U'来自SMTP的问候...','utf-8').encode()server = smtplib.SMTP(smtp_server,25) #SMTP协议的默认端口是25server.set_debuglevel(1) #打印一下和SMTP服务器交互的所有信息server.login(from_addr,password) #登录SMTP服务器#发送邮件,可以群发(所以to_addr为一个list,邮件正文为一个str,所以用as_string把MIMEText对象变成str)server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()如果我们要发送HTML邮件,而不是普通的纯文本文件怎么办??方法很简单,在构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变成html就可以了。
msg = MIMEText('<html><body><h1>Hello</h1>' + '<p>send by <a href="http://www.python.org">Python</a>...</p>' + '</body></html>', 'html', 'utf-8')
如果想在email中加上附件呢?带附件的邮件可以看做包含若干个部分的邮件:文本和各个附件本身。所以,可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,再继续往里面加上表示附件的MIMEBase对象即可:
#-*-coding:utf-8-*-from email.mime.text import MIMETextfrom email import encodersfrom email.utils import parseaddr,formataddrfrom email.header import Headerfrom email.mime.base import MIMEBasefrom email.mime.multipart import MIMEMultipartimport smtplib#编写一个函数_format_addr()来格式化一个邮件地址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 ))f_addr = raw_input('from:')passwd = raw_input('password:')t_addr = raw_input('to:')smtp_server = raw_input('smtp server:')#邮件对象是MIMEMultipartmsg = MIMEMultipart()msg['From'] = _format_addr(u'陈敏华bigTom <%s>' % f_addr)msg['To'] = _format_addr(u'管理员 <%s>' % t_addr)msg['Subject'] = Header(u'试试加个附件哈哈','utf-8').encode()#邮件正文是MIMETextmsg.attach(MIMEText('<html><body><h1>Hello</h1>' + '<p><img src="cid:0"></p>' + #把附件中的图片嵌入正文中 '</body></html>', 'html', 'utf-8'))#添加附件with open(u'H:/workspace/python/src/我要学python/email/carter.jpg','rb') as f: #设置附件的MIME和文件名 mime = MIMEBase('image','jpg',filename='carter.jpg') #加上必要的头信息 mime.add_header('Content-Disposition', 'attachment', filename='test.png') 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.login(f_addr,passwd)server.sendmail(f_addr,t_addr,msg.as_string())server.quit()
同时支持HTML和Plain格式
如果我们发送HTML邮件,收件人通过浏览器或者Outlook之类的软件是可以正常浏览邮件内容的,但是,如果收件人使用的设备太古老,查看不了HTML邮件怎么办?
办法是在发送HTML的同时再附加一个纯文本,如果收件人无法查看HTML格式的邮件,就可以自动降级查看纯文本邮件。
利用MIMEMultipart
就可以组合一个HTML和Plain,要注意指定subtype是alternative
:
msg = MIMEMultipart('alternative')msg['From'] = ...msg['To'] = ...msg['Subject'] = ...msg.attach(MIMEText('hello', 'plain', 'utf-8'))msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8'))# 正常发送msg对象...
加密SMTP
使用标准的25端口连接SMTP服务器时,使用的是明文传输,发送邮件的整个过程可能会被窃听。要更安全地发送邮件,可以加密SMTP会话,实际上就是先创建SSL安全连接,然后再使用SMTP协议发送邮件。
某些邮件服务商,例如Gmail,提供的SMTP服务必须要加密传输。我们来看看如何通过Gmail提供的安全SMTP发送邮件。
必须知道,Gmail的SMTP端口是587,因此,修改代码如下:
smtp_server = 'smtp.gmail.com'smtp_port = 587server = smtplib.SMTP(smtp_server, smtp_port)server.starttls()# 剩下的代码和前面的一模一样:server.set_debuglevel(1)...
只需要在创建SMTP
对象后,立刻调用starttls()
方法,就创建了安全连接。后面的代码和前面的发送邮件代码完全一样。
如果因为网络问题无法连接Gmail的SMTP服务器,请相信我们的代码是没有问题的,你需要对你的网络设置做必要的调整。
小结
使用Python的smtplib发送邮件十分简单,只要掌握了各种邮件类型的构造方法,正确设置好邮件头,就可以顺利发出。
构造一个邮件对象就是一个Messag
对象,如果构造一个MIMEText
对象,就表示一个文本邮件对象,如果构造一个MIMEImage
对象,就表示一个作为附件的图片,要把多个对象组合起来,就用MIMEMultipart
对象,而MIMEBase
可以表示任何对象。它们的继承关系如下:
Message+- MIMEBase +- MIMEMultipart +- MIMENonMultipart +- MIMEMessage +- MIMEText +- MIMEImage
这种嵌套关系就可以构造出任意复杂的邮件。你可以通过email.mime文档查看它们所在的包以及详细的用法。
POP收邮件:
SMTP用于发送邮件,那如果要收取邮件呢?收取邮件就是编写一个MUA作为客户端,从MDA把邮件获取到用户的电脑或者手机上。最常用的协议是POP3
python内置了一个poplib模块,实现了POP3协议。
注意:POP3收取的文本不是一个可读的邮件本身,要把收取的文本变成可阅读的邮件,还需要用email模块提供的各种类来解析原始文本。
所有,收取邮件分为两步:
一,用poplib把邮件的原始文本下载到本地。
二,用email解析原始文本,还原邮件对象。
我们来试一试:
#-*-coding:utf-8-*-import poplibimport emailfrom email.parser import Parserfrom email.header import decode_headerfrom email.utils import parseaddrdef 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 charsetdef decode_str(s): value, charset = decode_header(s)[0] if charset: value = value.decode(charset) return valuedef 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: ')#连接到POP3服务器server = poplib.POP3(pop3_server)#server.set_debuglevel(1)print(server.getwelcome())# 认证:server.user(email)server.pass_(password)#stat返回邮件数量和占用空间print('Messages: %s. Size: %s' % server.stat())#list返回所有邮件的编号resp, mails, octets = server.list()# 获取最新一封邮件, 注意索引号从1开始:resp, lines, octets = server.retr(len(mails))# 解析邮件:msg = Parser().parsestr('\r\n'.join(lines))# 打印邮件内容:print_info(msg)# 慎重:将直接从服务器删除邮件:# server.dele(len(mails))# 关闭连接:server.quit()
解析邮件
解析邮件的过程和上一节构造邮件正好相反,因此,先导入必要的模块:
import emailfrom email.parser import Parserfrom email.header import decode_headerfrom 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
- python之email
- Python之email
- python email
- python email
- python email
- Python Email
- python模块之email: 电子邮件编码解码 (一、解码邮件)
- python模块之email: 电子邮件编码解码 (二、编码邮件)
- python模块之email: 电子邮件编码解码 (一、解码邮件)
- python模块之email: 电子邮件编码解码 (一、解码邮件)
- python 发email
- python 发email
- Python发送Email
- Python 发送Email
- python 发送Email程序
- python 学习笔记 Email
- python的email模块
- python email mime使用
- Web前端开发之EasyUI
- iOS开发证书在多台机器上使用
- CashWithdrawReverse
- BUG之旅-MessageBox,Domodal不能显示对话框
- 代码的组织和部署
- python之email
- Android好奇宝宝_11_SwipeRefreshLayout原理浅析
- swift(2)
- java 判断字符串中是否包含汉字
- C#(64位系统) 解决"未能加载文件或程序集,或它的某一个依赖项..."
- Redis学习7--HyperLogLog
- ios开发过程中的相关加密算法
- 解决LAMP错误
- java web项目中使用tomcat的服务器,ajax时中文乱码的问题