python邮件发送接收

时间:2020-12-27 03:39:29
smtp协议的基本命令包括:
    HELO 向服务器标识用户身份
    MAIL 初始化邮件传输 mail from:
    RCPT 标识单个的邮件接收人;常在MAIL命令后面,可有多个rcpt to:
    DATA 在单个或多个RCPT命令后,表示所有的邮件接收人已标识,并初始化数据传输,以.结束
    VRFY 用于验证指定的用户/邮箱是否存在;由于安全方面的原因,服务器常禁止此命令
    EXPN 验证给定的邮箱列表是否存在,扩充邮箱列表,也常被禁用
    HELP 查询服务器支持什么命令
    NOOP 无操作,服务器应响应OK
    QUIT 结束会话
    RSET 重置会话,当前传输被取消
    MAIL FROM 指定发送者地址
    RCPT TO 指明的接收者地址
    一般smtp会话有两种方式,一种是邮件直接投递,就是说,比如你要发邮件給zzz@163.com,那就直接连接163.com的邮件服务器,把信投給zzz@163.com; 另一种是验证过后的发信,它的过程是,比如你要发邮件給zzz@163.com,你不是直接投到163.com,而是通过自己在sina.com的另一个邮箱来发。这样就要先连接sina.com的smtp服务器,然后认证,之后在把要发到163.com的信件投到sina.com上,sina.com会帮你把信投递到163.com。
    第一种方式的命令流程基本是这样:
      1. helo
      2. mail from
      3. rcpt to
      4. data
      5. quit
    但是第一种发送方式一般有限制的,就是rcpt to指定的这个邮件接收者必须在这个服务器上存在,否则是不会接收的。 先看看代码:
python邮件发送接收#-*- encoding: gb2312 -*-
python邮件发送接收
import os, sys, string
python邮件发送接收
import smtplib
python邮件发送接收
python邮件发送接收
# 邮件服务器地址
python邮件发送接收
mailserver = "smtp.163.com"
python邮件发送接收
# smtp会话过程中的mail from地址
python邮件发送接收
from_addr = "asfgysg@zxsdf.com"
python邮件发送接收
# smtp会话过程中的rcpt to地址
python邮件发送接收
to_addr = "zhaoweikid@163.com"
python邮件发送接收
# 信件内容
python邮件发送接收
msg = "test mail"
python邮件发送接收
python邮件发送接收svr 
= smtplib.SMTP(mailserver)
python邮件发送接收
# 设置为调试模式,就是在会话过程中会有输出信息
python邮件发送接收
svr.set_debuglevel(1)
python邮件发送接收
# helo命令,docmd方法包括了获取对方服务器返回信息
python邮件发送接收
svr.docmd("HELO server")
python邮件发送接收
# mail from, 发送邮件发送者
python邮件发送接收
svr.docmd("MAIL FROM: <%s>" % from_addr)
python邮件发送接收
# rcpt to, 邮件接收者
python邮件发送接收
svr.docmd("RCPT TO: <%s>" % to_addr)
python邮件发送接收
# data命令,开始发送数据
python邮件发送接收
svr.docmd("DATA")
python邮件发送接收
# 发送正文数据
python邮件发送接收
svr.send(msg)
python邮件发送接收
# 比如以 . 作为正文发送结束的标记,用send发送的,所以要用getreply获取返回信息
python邮件发送接收
svr.send(" .")
python邮件发送接收svr.getreply()
python邮件发送接收
# 发送结束,退出
python邮件发送接收
svr.quit()


    注意的是,163.com是有反垃圾邮件功能的,想上面的这种投递邮件的方法不一定能通过反垃圾邮件系统的检测的。所以一般不推荐个人这样发送。
    第二种有点不一样:
      1.ehlo
      2.auth login
      3.mail from
      4.rcpt to
      5.data
      6.quit
    相对于第一种来说,多了一个认证过程,就是auth login这个过程。

#-*- encoding: gb2312 -*-
import os, sys, string
import smtplib
import base64

# 邮件服务器地址
mailserver = "smtp.163.com"
# 邮件用户名
username = "xxxxxx@163.com"
# 密码
password = "xxxxxxx"
# smtp会话过程中的mail from地址
from_addr = "xxxxxx@163.com"
# smtp会话过程中的rcpt to地址
to_addr = "yyyyyy@163.com"
# 信件内容
msg = "my test mail"

svr 
= smtplib.SMTP(mailserver)
# 设置为调试模式,就是在会话过程中会有输出信息
svr.set_debuglevel(1)
# ehlo命令,docmd方法包括了获取对方服务器返回信息
svr.docmd("EHLO server")
# auth login 命令
svr.docmd("AUTH LOGIN")
# 发送用户名,是base64编码过的,用send发送的,所以要用getreply获取返回信息
svr.send(base64.encodestring(username))
svr.getreply()
# 发送密码
svr.send(base64.encodestring(password))
svr.getreply()
# mail from, 发送邮件发送者
svr.docmd("MAIL FROM: <%s>" % from_addr)
# rcpt to, 邮件接收者
svr.docmd("RCPT TO: <%s>" % to_addr)
# data命令,开始发送数据
svr.docmd("DATA")
# 发送正文数据
svr.send(msg)
# 比如以 . 作为正文发送结束的标记
svr.send(" .")
svr.getreply()
# 发送结束,退出
svr.quit()

   
    上面说的是最普通的情况,但是不能忽略的是现在好多企业邮件是支持安全邮件的,就是通过SSL发送的邮件,这个怎么发呢?SMTP对SSL安全邮件的支持有两种方案,一种老的是专门开启一个465端口来接收ssl邮件,另一种更新的做法是在标准的25端口的smtp上增加一个starttls的命令来支持。
    看看第一种怎么办:

#-*- encoding: gb2312 -*-
import os, sys, string, socket
import smtplib


class SMTP_SSL (smtplib.SMTP):
    
def __init__(self, host='', port=465, local_hostname=None, key=None, cert=None):
        self.cert 
= cert
        self.key 
= key
        smtplib.SMTP.
__init__(self, host, port, local_hostname)
        
    
def connect(self, host='localhost', port=465):
        
if not port and (host.find(':'== host.rfind(':')):
            i 
= host.rfind(':')
            
if i >= 0:
                host, port 
= host[:i], host[i+1:]
                
try: port = int(port)
                
except ValueError:
                    
raise socket.error, "nonnumeric port"
        
if not port: port = 654
        
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
        msg 
= "getaddrinfo returns an empty list"
        self.sock 
= None
        
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
            af, socktype, proto, canonname, sa 
= res
            
try:
                self.sock 
= socket.socket(af, socktype, proto)
                
if self.debuglevel > 0: print>>stderr, 'connect:', (host, port)
                self.sock.connect(sa)
                
# 新增加的创建ssl连接
                sslobj = socket.ssl(self.sock, self.key, self.cert)
            
except socket.error, msg:
                
if self.debuglevel > 0: 
                    
print>>stderr, 'connect fail:', (host, port)
                
if self.sock:
                    self.sock.close()
                self.sock 
= None
                
continue
            
break
        
if not self.sock:
            
raise socket.error, msg

        
# 设置ssl
        self.sock = smtplib.SSLFakeSocket(self.sock, sslobj)
        self.file 
= smtplib.SSLFakeFile(sslobj);

        (code, msg) 
= self.getreply()
        
if self.debuglevel > 0: print>>stderr, "connect:", msg
        
return (code, msg)
        
if __name__ == '__main__':
    smtp 
= SMTP_SSL('192.168.2.10')
    smtp.set_debuglevel(
1)
    smtp.sendmail(
"zzz@xxx.com""zhaowei@zhaowei.com""xxxxxxxxxxxxxxxxx")
    smtp.quit()

   
    这里我是从原来的smtplib.SMTP派生出了新的SMTP_SSL类,它专门来处理ssl连接。我这里测试的192.168.2.10是我自己的测试服务器.
    第二种是新增加了starttls的命令,这个很简单,smtplib里就有这个方法,叫smtplib.starttls()。当然,不是所有的邮件系统都支持安全邮件的,这个需要从ehlo的返回值里来确认,如果里面有starttls,才表示支持。相对于发送普通邮件的第二种方法来说,只需要新增加一行代码就可以了:

#-*- encoding: gb2312 -*-
import os, sys, string
import smtplib
import base64

# 邮件服务器地址
mailserver = "smtp.163.com"
# 邮件用户名
username = "xxxxxx@163.com"
# 密码
password = "xxxxxxx"
# smtp会话过程中的mail from地址
from_addr = "xxxxxx@163.com"
# smtp会话过程中的rcpt to地址
to_addr = "yyyyyy@163.com"
# 信件内容
msg = "my test mail"

svr 
= smtplib.SMTP(mailserver)
# 设置为调试模式,就是在会话过程中会有输出信息
svr.set_debuglevel(1)
# ehlo命令,docmd方法包括了获取对方服务器返回信息,如果支持安全邮件,返回值里会有starttls提示
svr.docmd("EHLO server")
svr.starttls()  
# <------ 这行就是新加的支持安全邮件的代码!
#
 auth login 命令
svr.docmd("AUTH LOGIN")
# 发送用户名,是base64编码过的,用send发送的,所以要用getreply获取返回信息
svr.send(base64.encodestring(username))
svr.getreply()
# 发送密码
svr.send(base64.encodestring(password))
svr.getreply()
# mail from, 发送邮件发送者
svr.docmd("MAIL FROM: <%s>" % from_addr)
# rcpt to, 邮件接收者
svr.docmd("RCPT TO: <%s>" % to_addr)
# data命令,开始发送数据
svr.docmd("DATA")
# 发送正文数据
svr.send(msg)
# 比如以 . 作为正文发送结束的标记
svr.send(" .")
svr.getreply()
# 发送结束,退出
svr.quit()


注意: 以上的代码为了方便我都没有判断返回值,严格说来,是应该判断一下返回的代码的,在smtp协议中,只有返回代码是2xx或者3xx才能继续下一步,返回4xx或5xx的,都是出错了

python模块之email: 电子邮件编码解码 (二、编码邮件)

用email模块来生成邮件也是很简单的,只是需要一些mime的基础知识。下面看看一点mime基础。
MIME消息由消息头和消息体两大部分组成,在邮件里就是邮件头和邮件体。邮件头与邮件体之间以空行进行分隔。这点可以用文本编辑器(比如记事本)查看一个邮件的源文件就可以清除看到。outlook和foxmail自己就有查看源文件的功能。
  邮件头包含了发件人、收件人、主题、时间、MIME版本、邮件内容的类型等重要信息。每条信息称为一个域,由域名后加“: ”和信息内容构成,可以是一行,较长的也可以占用多行。域的首行必须“顶头”写,即左边不能有空白字符(空格和制表符);续行则必须以空白字符打头,且第一个空白字符不是信息本身固有的。
  邮件体包含邮件的内容,它的类型由邮件头的“Content-Type”域指出。最常见的类型有text/plain(纯文本)和text/html(超文本)。邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。常见的multipart类型有三种:multipart/mixed, multipart/related和multipart/alternative。从它们的名称,不难推知这些类型各自的含义和用处。它们之间的层次关系可归纳为下图所示:
python邮件发送接收
  可以看出,如果在邮件中要添加附件,必须定义multipart/mixed段;如果存在内嵌资源,至少要定义 multipart/related段;如果纯文本与超文本共存,至少要定义multipart/alternative段。生成邮件就是要生成这各个MIME部分。email模块对这些处理都是包装好的,看看生成方法:

#-*- encoding: gb2312 -*-
import email
import string, sys, os, email
import time

class MailCreator:
    
def __init__(self):
        
# 创建邮件的message对象
        self.msg = email.Message.Message()
        self.mail 
= ""    
        
    
def create(self, mailheader, maildata, mailattachlist=[]):
        
# mailheader 是dict类型,maildata是list, 且里面第一项为纯文本类型,第二项为html.
        # mailattachlist 是list, 里面为附件文件名
        if not mailheader or not maildata:
            
return
        
        
for k in mailheader.keys():
            
# 对subject要作特殊处理,中文要转换一下。
            # 比如 "我的一个测试邮件" 就要转换为 =?gb2312?b?ztK1xNK7uPay4srU08q8/g==?=
            if k == 'subject':
                self.msg[k] 
= email.Header.Header(mailheader[k], 'gb2312')           
            
else:
                self.msg[k] 
= mailheader[k]
        
# 创建纯文本部分
        body_plain = email.MIMEText.MIMEText(maildata[0], _subtype='plain', _charset='gb2312')
        body_html 
= None
        
# 创建html部分,这个是可选的
        if maildata[1]:
            body_html 
= email.MIMEText.MIMEText(maildata[1], _subtype='html', _charset='gb2312')
        
        
        
# 创建一个multipart, 然后把前面的文本部分和html部分都附加到上面,至于为什么,可以看看mime相关内容
        attach=email.MIMEMultipart.MIMEMultipart()
        attach.attach(body_plain)
        
if body_html:
            attach.attach(body_html)
        
# 处理每一个附件
        for fname in mailattachlist:
            attachment
=email.MIMEText.MIMEText(email.Encoders._bencode(open(fname,'rb').read()))
            
# 这里设置文件类型,全部都设置为Application.当然也可以是Image,Audio什么的,这里不管那么多
            attachment.replace_header('Content-type','Application/octet-stream;name="'+os.path.basename(fname)+'"')
            
# 一定要把传输编码设置为base64,因为这里默认就是用的base64
            attachment.replace_header('Content-Transfer-Encoding''base64')
            attachment.add_header(
'Content-Disposition','attachment;filename="'+os.path.basename(fname)+'"')
            attach.attach(attachment)
        
# 生成最终的邮件            
        self.mail = self.msg.as_string()[:-1+ attach.as_string()
        
        
return self.mail

if __name__ == '__main__':
    mc 
= MailCreator()
    header 
= {'from''zhaowei@163.com''to':'weizhao@163.com''subject':'我的一个测试邮件'}
    data 
= ['plain text information''<font color="red">html text information</font>']
    
if sys.platform == 'win32':
        attach 
= ['c:\windows\clock.avi']
    
else:
        attach 
= ['/bin/cp']
    
    mail 
= mc.create(header, data, attach)
    
    f 
= open("test.eml""wb")
    f.write(mail)
    f.close()

 这里我自己封装了一个类来做处理,大体的过程就是:
1. 先创建message对象: email.Message.Message()
2. 创建MIMEMultipart对象:email.MIMEMultipart.MIMEMultipart()
3. 创建各个MIMEText对象,并把他们attach到MIMEMultipart里,这里的MIMEText其实不仅仅是text, 也包括image, application, audio等等。
4. 生成最终邮件。

python模块之email: 电子邮件编码解码 (一、解码邮件)

 python自带的email模块是个很有意思的东西,它可以对邮件编码解码,用来处理邮件非常好用。
处理邮件是一个很细致的工作,尤其是解码邮件,因为它的格式变化太多了,下面先看看一个邮件的源文件:
Received: from 192.168.208.56 ( 192.168.208.56 [192.168.208.56] ) by
 ajax-webmail-wmsvr37 (Coremail) ; Thu, 12 Apr 2007 12:07:48 +0800 (CST)
Date: Thu, 12 Apr 2007 12:07:48 +0800 (CST)
From: user1 <xxxxxxxx@163.com>
To: zhaowei <zhaoweikid@163.com>
Message-ID: <31571419.200911176350868321.JavaMail.root@bj163app37.163.com>
Subject: =?gbk?B?u+nJtA==?=
MIME-Version: 1.0
Content-Type: multipart/Alternative;
    boundary="----=_Part_21696_28113972.1176350868319"

------=_Part_21696_28113972.1176350868319
Content-Type: text/plain; charset=gbk
Content-Transfer-Encoding: base64

ztLS0b+qyrzS1M6qysfSu7j20MfG2ru70ru0zqOs1K3AtMrH0ru49tTCtffSu7TOztLDx8/W1NrT
prjDysew67XjssXE3MjI1ebC6bezICAg
------=_Part_21696_28113972.1176350868319
Content-Type: text/html; charset=gbk
Content-Transfer-Encoding: quoted-printable

<DIV>=CE=D2=D2=D1=BF=AA=CA=BC=D2=D4=CE=AA=CA=C7=D2=BB=B8=F6=D0=C7=C6=DA=BB=
=BB=D2=BB=B4=CE=A3=AC=D4=AD=C0=B4=CA=C7=D2=BB=B8=F6=D4=C2=B5=F7=D2=BB=B4=CE=
</DIV>
<DIV>=CE=D2=C3=C7=CF=D6=D4=DA=D3=A6=B8=C3=CA=C7=B0=EB=B5=E3=B2=C5=C4=DC=C8=
=C8</DIV>
<DIV>=D5=E6=C2=E9=B7=B3</DIV>
------=_Part_21696_28113972.1176350868319--

    上面的就是以封邮件的源文件,从第一行到第一个空行之间的为信件头,后面的就是信件体了。把上面的信息复制下来存到一个叫xxx.eml的文件里,用鼠标双击就可以看到内容,当然看到的是解码后的,是outlook帮你解码了。
    看看email模块怎么处理这个邮件,假设信件已经存为xxx.eml。

python邮件发送接收#-*- encoding: gb2312 -*-
python邮件发送接收
import email
python邮件发送接收
python邮件发送接收fp 
= open("xxx.eml""r")
python邮件发送接收msg 
= email.message_from_file(fp) # 直接文件创建message对象,这个时候也会做初步的解码
python邮件发送接收
subject = msg.get("subject"# 取信件头里的subject, 也就是主题
python邮件发送接收#
 下面的三行代码只是为了解码象=?gbk?Q?=CF=E0=C6=AC?=这样的subject
python邮件发送接收
= email.Header.Header(subject)
python邮件发送接收dh 
= email.Header.decode_header(h)
python邮件发送接收subject 
= dh[0][0]
python邮件发送接收
print "subject:", subject
python邮件发送接收
print "from: ", email.utils.parseaddr(msg.get("from"))[1# 取from
python邮件发送接收
print "to: ", email.utils.parseaddr(msg.get("to"))[1# 取to
python邮件发送接收

python邮件发送接收fp.close()

    这段代码可以把一封邮件中的主题、发件人、收件人解析出来。email.utils.parseaddr是用来专门解析邮件地址的,原因是邮件地址很多时候在原文里是这样写的:user1 <xxxxxxxx@163.com>, email.utils.parseaddr就可以把它解析为一个列表,第一项是user1, 第二项是xxxxxxxx@163.com, 这里只显示了后面以部分。
    前面那段代码只是解析了信件头,接着解析信件体吧。信体里可能有纯文本的plain和html两部分,也可能有附件。这里需要mime的知识,详细介绍可以从网上搜搜。我这里就不说了,下面看看怎么解析的:

python邮件发送接收#-*- encoding: gb2312 -*-
python邮件发送接收
import email
python邮件发送接收
python邮件发送接收fp 
= open("xxx.eml""r")
python邮件发送接收msg 
= email.message_from_file(fp)
python邮件发送接收
python邮件发送接收
# 循环信件中的每一个mime的数据块
python邮件发送接收
for par in msg.walk():
python邮件发送接收    
if not par.is_multipart():# 这里要判断是否是multipart,是的话,里面的数据是无用的,至于为什么可以了解mime相关知识。
python邮件发送接收
        name = par.get_param("name"#如果是附件,这里就会取出附件的文件名
python邮件发送接收
        if name:
python邮件发送接收            
#有附件
python邮件发送接收
            # 下面的三行代码只是为了解码象=?gbk?Q?=CF=E0=C6=AC.rar?=这样的文件名
python邮件发送接收
            h = email.Header.Header(name)
python邮件发送接收            dh 
= email.Header.decode_header(h)
python邮件发送接收            fname 
= dh[0][0]
python邮件发送接收            
print '附件名:', fname
python邮件发送接收            data 
= par.get_payload(decode=True) # 解码出附件数据,然后存储到文件中
python邮件发送接收
            
python邮件发送接收            
try:
python邮件发送接收                f 
= open(fname, 'wb'#注意一定要用wb来打开文件,因为附件一般都是二进制文件
python邮件发送接收
            except:
python邮件发送接收                
print '附件名有非法字符,自动换一个'
python邮件发送接收                f 
= open('aaaa''wb')
python邮件发送接收            f.write(data)
python邮件发送接收            f.close()
python邮件发送接收        
else:
python邮件发送接收            
#不是附件,是文本内容
python邮件发送接收
            print par.get_payload(decode=True) # 解码出文本内容,直接输出来就可以了。
python邮件发送接收
        
python邮件发送接收        
print '+'*60 # 用来区别各个部分的输出
python模块之poplib: 用pop3收取邮件

 python的poplib模块是用来从pop3收取邮件的,也可以说它是处理邮件的第一步。
    POP3协议并不复杂,它也是采用的一问一答式的方式,你向服务器发送一个命令,服务器必然会回复一个信息。pop3命令码如下:
 命令 poplib方法    参数     状态     描述
-----------------------------------------------------------------------------------------------
USER  user     username  认可  用户名,此命令与下面的pass命令若成功,将导致状态转换
PASS  pass_   password  认可  用户密码   
APOP  apop   Name,Digest 认可   Digest是MD5消息摘要
-----------------------------------------------------------------------------------------------
STAT  stat    None     处理  请求服务器发回关于邮箱的统计资料,如邮件总数和总字节数
UIDL  uidl   [Msg#]    处理  返回邮件的唯一标识符,POP3会话的每个标识符都将是唯一的
LIST  list   [Msg#]    处理  返回邮件数量和每个邮件的大小
RETR  retr   [Msg#]     处理  返回由参数标识的邮件的全部文本
DELE  dele   [Msg#]     处理  服务器将由参数标识的邮件标记为删除,由quit命令执行
RSET  rset   None      处理   服务器将重置所有标记为删除的邮件,用于撤消DELE命令
TOP   top     [Msg#]    处理   服务器将返回由参数标识的邮件前n行内容,n必须是正整数
NOOP  noop   None    处理  服务器返回一个肯定的响应
----------------------------------------------------------------------------------------------
QUIT  quit     None    更新     

    python的poplib也针对这些命令分别提供了对应的方法,上面在第二列里已经标出来。收取邮件的过程一般是:
1. 连接pop3服务器 (poplib.POP3.__init__)
2. 发送用户名和密码进行验证 (poplib.POP3.user poplib.POP3.pass_)
3. 获取邮箱中信件信息 (poplib.POP3.stat)
4. 收取邮件 (poplib.POP3.retr)
5. 删除邮件 (poplib.POP3.dele)
6. 退出 (poplib.POP3.quit)
    注意的是,上面我在括号里写的是使用什么方法来完成这个操作,在实际的代码中不能那样写,应该是创建poplib.POP3的对象,然后,调用这个对象的方法。比如:
poplib.POP3.quit
应该理解为
a = poplib.POP3(host)
a.quit()
下面看看实际的代码:

#-*- encoding: gb2312 -*-
import os, sys, string
import poplib

# pop3服务器地址
host = "pop3.163.com"
# 用户名
username = "xxxxxx@163.com"
# 密码
password = "xxxxxxx"
# 创建一个pop3对象,这个时候实际上已经连接上服务器了
pp = poplib.POP3(host)
# 设置调试模式,可以看到与服务器的交互信息
pp.set_debuglevel(1)
# 向服务器发送用户名
pp.user(username)
# 向服务器发送密码
pp.pass_(password)
# 获取服务器上信件信息,返回是一个列表,第一项是一共有多上封邮件,第二项是共有多少字节
ret = pp.stat()
print ret
# 需要取出所有信件的头部,信件id是从1开始的。
for i in range(1, ret[0]+1):
    
# 取出信件头部。注意:top指定的行数是以信件头为基数的,也就是说当取0行,
    # 其实是返回头部信息,取1行其实是返回头部信息之外再多1行。
    mlist = pp.top(i, 0)
    
print 'line: ', len(mlist[1])
# 列出服务器上邮件信息,这个会对每一封邮件都输出id和大小。不象stat输出的是总的统计信息
ret = pp.list()
print ret
# 取第一封邮件完整信息,在返回值里,是按行存储在down[1]的列表里的。down[0]是返回的状态信息
down = pp.retr(1)
print 'lines:', len(down)
# 输出邮件
for line in down[1]:
    
print line
# 退出
pp.quit()

在有些地方,有安全邮件这一说,其实是对pop3做了ssl加密。这样的,poplib一样可以处理,只不过不是用POP3这个类,而是用POP3_SSL, 他们的方法都一样。因此支持ssl在上面代码中,替换创建pop3对象的一行为:

python邮件发送接收pp = poplib.POP3_SSL(host)