参考:Send_Recv_Mail Send_Recv_Mail
Send
# 流程: 发件人 -> MUA ---(SMTP)---> MTA---(SMTP)--->MTA -> MDA <--(POP3/IMAP)--- MUA <- 收件人
# 要编写程序来发送和接收邮件,本质上就是:编写MUA把邮件发到MTA;编写MUA从MDA上收邮件。
# SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。
# Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.utils import parseaddr, formataddr
import smtplib
# 编码
def _format_addr(s):
name, addr = parseaddr(s)
# 如果包含中文,需要通过Header对象进行编码
return formataddr((Header(name, 'utf-8').encode(), addr))
# 发件人信息
from_addr = '6781@qq.com'
password = 'password'
# 收件人信息
to_addr = '6781@qq.com'
# SMTP 服务器
smtp_server = 'smtp.qq.com'
# 163:465或者994 QQ: 465或587
SSL_part=587
# 一个邮件对象就是一个Messag对象,如果构造一个MIMEText对象,就表示一个文本邮件对象,
# 如果构造一个MIMEImage对象,就表示一个作为附件的图片,要把多个对象组合起来,就用MIMEMultipart对象,
# 而MIMEBase可以表示任何对象。
# Message
# +- MIMEBase
# +- MIMEMultipart
# +- MIMENonMultipart
# +- MIMEMessage
# +- MIMEText
# +- MIMEImage
# ---------------------------纯文本-------------------------------------
# 第一个参数就是邮件正文,第二个参数是MIME的subtype,plain表示纯文本,html表示HTML页面,最终的MIME就是'text/plain',用utf-8编码保证多语言兼容性。
# 发送HTML邮件,在构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变为html
# msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
# msg = MIMEText('<html><body><h1>Hello</h1>'+'<p>send by <a href="http://www.python.org">Python</a>...</p>'+'</body></html>', 'html', 'utf-8')
# ---------------------------含附件-------------------------------------
# 创建MIMEMultipart()实例,然后构造附件,如果有多个附件,可依次构造.
# MIMEMultipart(alternative) :表示在收件方如果html无法正常解析,就是用纯文本。发件方要同时加入plain html
# MIMEMultipart() :只发送plain或者html
msg = MIMEMultipart('alternative')
# 邮件正文是MIMEText
msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))
# HTML内嵌图片要把图片当附件传入,利用编号调用,编号在附件的文件头中确定
msg.attach(MIMEText('<html><body><h1>Hello</h1>' +'<p><img src="cid:1"></p>' +'</body></html>', 'html', 'utf-8'))
# 添加附件就是加上一个MIMEBase,从本地读取文件:
with open('Mail\pdf.pdf', 'rb') as f:
# 设置附件的MIME和文件名。
mime = MIMEBase('doc', 'pdf', filename='show_file_name.pdf')
# 加上必要的头信息:
mime.add_header('Content-Disposition', 'attachment', filename='show_file_name.pdf')
# 文件ID,便于HTML调用
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)
# 读取本地文本文件
with open('Mail\send.py', 'rb') as f:
# 构造附件
att = MIMEText(f.read(), 'base64', 'utf-8')
# 文件头
att["Content-Type"] = 'application/octet-stream'
att["Content-Disposition"] = 'attachment; filename="show_file_name.py"'
msg.attach(att)
# 添加图片
with open('Mail\img.png', 'rb') as fp:
msgImage = MIMEImage(fp.read())
# 定义图片 ID,在 HTML 文本中引用
msgImage.add_header('Content-ID', '<1>')
mime.add_header('X-Attachment-Id', '1')
# 文件头
msgImage["Content-Disposition"] = 'attachment; filename="show_file_name.png"'
msg.attach(msgImage)
# 邮件主题、如何显示发件人、收件人等信息并不是通过SMTP协议发给MTA,而是包含在发给MTA的文本中的
msg['From'] = _format_addr('2812 <%s>' % from_addr)
# 接收的是字符串而不是list,如果有多个邮件地址,用,分隔即可。SSL_pa
msg['To'] = _format_addr('2750 <%s>' % to_addr)
msg['Subject'] = Header('SMTP send', 'utf-8').encode()
# SSL加密传输
# Version 1
# server=smtplib.SMTP(smtp_server,SSL_part)
# server.starttls()
# Version 2
server =smtplib.SMTP_SSL(smtp_server)
# 打印出和SMTP服务器交互的所有信息
server.set_debuglevel(1)
# 登录SMTP服务器
server.login(from_addr, password)
# 可以一次发给多个人,所以传入一个list,邮件正文是一个str,as_string()把MIMEText对象变成str。
# ['****@mail.com','****@mail.com']
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
Recv
# POP3协议收取的不是一个已经可以阅读的邮件本身,而是邮件的原始文本,这和SMTP协议很像,SMTP发送的也是经过编码后的一大段文本。
# 要把POP3收取的文本变成可以阅读的邮件,还需要用email模块提供的各种类来解析原始文本,变成可阅读的邮件对象。
# 第一步:用poplib把邮件的原始文本下载到本地;
# 第二部:用email解析原始文本,还原为邮件对象。
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr
import poplib
# 输入邮件地址, 口令和POP3服务器地址:
email = '6781@qq.com'
password = 'password'
pop3_server ='pop.qq.com'
# 猜测文字编码
# 首先在message中寻找编码,如果没有,就在header的Content-Type中寻找
def 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 charset
# 解析文本
def decode_str(s):
# decode_header()返回一个list,这里只返回了第一个元素
value, charset = decode_header(s)[0]
if charset:
value = value.decode(charset)
return value
# Message对象本身可能是一个MIMEMultipart对象,即包含嵌套的其他MIMEBase对象,
# 递归地打印出Message对象的层次结构
def print_info(msg, indent=0):
# indent用于缩进显示。
if indent == 0:
# 第一层缩进,包含邮件基本信息
for header in ['From', 'To', 'Subject']:
value = msg.get(header, '')
if value:
if header=='Subject':
# 解析主题
value = decode_str(value)
else:
# 解析收发件人信息,然后转换成unicode对象。
hdr, addr = parseaddr(value)
name = decode_str(hdr)
# u表示将后面跟的字符串以unicode格式存储
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()
# html /plain
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))
# 下载附件
file_name = msg.get_filename()
file_name='Mail\Recv_'+file_name
with open(file_name,'wb') as f:
data=msg.get_payload(decode=True)
f.write(data)
# 连接到POP3服务器:
server = poplib.POP3_SSL(pop3_server)
# 可以打开或关闭调试信息:
server.set_debuglevel(1)
# 可选:打印POP3服务器的欢迎文字:
print(server.getwelcome().decode('utf-8'))
# 身份认证:
server.user(email)
server.pass_(password)
# stat()返回邮件数量和占用空间:
print('Messages: %s. Size: %s' % server.stat())
# list()返回所有邮件的编号:
# mails包含返回的所有邮件
# 返回一个3元祖(返回信息, 消息列表, 消息的大小),如果指定index,就只返回指定消息的数据
resp, mails, octets = server.list()
# 可以查看返回的列表类似[b'1 82923', b'2 2184', ...]
print(mails)
# 获取最新一封邮件, 注意索引号从1开始:
index = len(mails)
# lines存储了邮件的原始文本的每一行,
# 可以获得整个邮件的原始文本:
# 获取详细index,设置为已读,返回3元组(返回信息, 消息msgnum的所以内容, 消息的字节数),
# 如果指定index,就只返回指定消息的数据
resp, lines, octets = server.retr(index)
msg_content = b'
'.join(lines).decode('utf-8')
# 稍后解析出邮件:
msg = Parser().parsestr(msg_content)
print_info(msg)
# 可以根据邮件索引号直接从服务器删除邮件:
# server.dele(index)
# 关闭连接:
server.quit()