python3基础知识复习 -- 电子邮件

电子邮件

邮件收发流程

  • 假设我们自己的电子邮件地址是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不会直接到达对方的电脑, 对方要取到邮件,必须通过MUA从MDA上把邮件取到自己的电脑上。

  • 一封电子邮件的旅程就是:

    发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
    
  • 有了上述基本概念,要编写程序来发送和接收邮件,本质上就是:

    1. 编写MUA把邮件发到MTA;
    2. 编写MUA从MDA上收邮件。
    • 发邮件时,MUA和MTA使用的协议就是SMTP:Simple Mail Transfer Protocol,后面的MTA到另一个MTA也是用SMTP协议。

    • 收邮件时,MUA和MDA使用的协议有两种:POP:Post Office Protocol,目前版本是3,俗称POP3;IMAP:Internet Message Access Protocol,目前版本是4,优点是不但能取邮件,还可以直接操作MDA上存储的邮件,比如从收件箱移到垃圾箱.

  • 常用smtp/pop3地址, 参考自如何开启outlook邮箱的pop3和smtp_常用邮箱客户端设置指南_weixin_39578674的博客-CSDN博客

    • 需要邮箱授权码:使用第三方客户端登录时,需要在设置界面打开相应的服务,发送相应短信后,邮箱会生成授权码,在配置邮件客户端时,需要输入密码的地方输入该授权码即可。

      # QQ邮箱
      QQ邮箱的POP3/SMTP 和IMAP服务器地址如下(收件方式可以是POP3或者IMAP,发件方式为SMTP):
      
      POP3: pop.qq.com,使用SSL,端口号995
      IMAP: imap.qq.com, 使用SSL,端口号993
      SMTP: smtp.qq.com, 使用SSL,端口号465或587
      账户名:您的QQ邮箱账户名(如果您是VIP帐号或Foxmail帐号,账户名需要填写完整的邮件地址)
      密码:上一步生成的QQ邮箱授权码
      电子邮件地址:您的QQ邮箱的完整邮件地址(与账户名相同)
      
      # 网易邮箱
      网易邮箱有126.com,163.com和yeah.net三个域名,对应的POP3/SMTP 和IMAP服务器地址分别如下(收件方式可以是POP3或者IMAP,发件方式为SMTP):
      
      POP3: pop.126.com, pop.163.com ,pop.yeah.net, 使用SSL,端口号995
      IMAP: imap.126.com,imap.163.com, imap.yeah.net, 使用SSL,端口号993
      SMTP: smtp.126.com, smtp.163.com, smtp.yeah.net, 使用SSL,端口号465或587
      账户名:您的网易邮箱账户名
      密码:上一步生成的邮箱授权码
      电子邮件地址:邮箱的完整邮件地址(与账户名相同)
      
    • outlook邮箱

      微软Outlook邮箱的POP3/SMTP 和IMAP服务器地址如下(收件方式可以是POP3或者IMAP,发件方式为SMTP)需要注意,微软邮箱设置和上述邮箱的协议是有所区别的:
      
      POP3: outlook.office365.com,使用TLS,端口号995
      IMAP: outlook.office365.com,使用TLS,端口号993
      SMTP: smtp.office365.com ,使用 starttls(),端口号587
      账户名:您的outlook邮箱账户名
      密码:您的outlook邮箱密码
      电子邮件地址:邮箱的完整邮件地址
      

SMTP发送邮件

Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。

模块:email负责构造邮件,smtplib负责发送邮件。

纯文本 text/plain
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import smtplib

def _format_addr(s): # 用于格式化一个邮件地址, like James Bond <james.bond@movie.com>
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))

from_addr = input('From: ') # 输入Email地址和口令
password = input('Password: ')
to_addr = input('To: ') # 输入收件人地址
smtp_server = input('SMTP server: ') # 输入SMTP服务器地址

"""
from_addr = "meifajia@outlook.com"
to_addr = "1819818024@qq.com"
smtp_server = "smtp.office365.com"
"""

msg = MIMEText('hello, test message from PY...', 'plain', 'utf-8')

msg['From'] = _format_addr('国粹 <%s>' % from_addr)
msg['To'] = _format_addr('ZSUS <%s>' % to_addr) # 接收的是字符串而不是list,如果有多个邮件地址,用,分隔即可。注意很多邮件服务商在显示邮件时,会把收件人名字自动替换为用户注册的名字
msg['Subject'] = Header('Testing,犇...', 'utf-8').encode()

server = smtplib.SMTP(smtp_server, 587) # SMTP协议默认端口是25, 
server.starttls() # 创建SSL安全连接,加密SMTP
server.set_debuglevel(1) # 打印和SMTP server的交互信息
server.login(from_addr, password) # 登录SMTP server
server.sendmail(from_addr, [to_addr], msg.as_string()) # 可以发送给多人所以用list, as_string将对象变成str
server.quit()

1

text/html

msg内容变更为

html = '<html><body><h1>hello Guys</h1><p>5rd test is sent by <a href="https://www.baidu.com">baidu</a>...</p><p><img src="cid:0"></p></body></html>'

msg = MIMEText(html, 'html', 'utf-8')

2

添加附件

带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,再继续往里面加上表示附件的MIMEBase对象即可。

from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
...

# 邮件对象:
msg = MIMEMultipart()

msg['From'] = _format_addr('国粹 <%s>' % from_addr)
msg['To'] = _format_addr('ZSUS <%s>' % to_addr) 
msg['Subject'] = Header('Testing,犇...', 'utf-8').encode()

# 邮件正文是MIMEText:
msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
with open('/Users/michael/Downloads/test.png', 'rb') as f:
    # 设置附件的MIME和文件名,这里是png类型:
    mime = MIMEBase('image', 'png', filename='test.png')
    # 加上必要的头信息:
    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)

# 发送邮件。。。

3

发送图片

按照发送附件的方式,先把邮件作为附件添加进去,然后,在HTML中通过引用src="cid:0"就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的cid:x即可。

把上面代码加入MIMEMultipartMIMETextplain改为html,然后在适当的位置引用图片

# cid:0 是指向前面附件中的header信息('Content-ID', '<0>')
html = '<html><body><h1>hello Guys</h1><p> test is sent by <a href="https://www.baidu.com">baidu</a>...</p><p><img src="cid:0"></p></body></html>'

msg.attach(MIMEText(html, 'html', 'utf-8'))

5

同时支持HTML和Plain格式

如果收件人无法查看HTML格式的邮件,就可以自动降级查看纯文本邮件。

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对象...
Summary

构造一个邮件对象就是一个Messag对象,如果构造一个MIMEText对象,就表示一个文本邮件对象,如果构造一个MIMEImage对象,就表示一个作为附件的图片,要把多个对象组合起来,就用MIMEMultipart对象,而MIMEBase可以表示任何对象。它们的继承关系如下:

Message
+- MIMEBase
   +- MIMEMultipart
   +- MIMENonMultipart
      +- MIMEMessage
      +- MIMEText
      +- MIMEImage

这种嵌套关系就可以构造出任意复杂的邮件。你可以通过email.mime文档查看它们所在的包以及详细的用法。

POP3收邮件

收取邮件就是编写一个MUA作为客户端,从MDA把邮件获取,Python内置一个poplib模块,实现了POP3协议,可以直接用来收邮件。

收取邮件分两步:

第一步:用poplib把邮件的原始文本下载到本地;

第二部:用email解析原始文本,还原为message对象, 然后用适当的形式把内容展示出来。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"POP3 service"

import poplib
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr

email = input('Email: ')
password = input('Password: ')
pop3_server = input('POP3 Server: ')

"""
email = "meifajia@outlook.com"
password = "xxxx"
pop3_server = "outlook.office365.com"
"""

server = poplib.POP3_SSL(pop3_server, 995) # 启用SSL模式,要去邮件客户端设置开启允许第三方客户端的POP3协议
server.set_debuglevel(1)
print(server.getwelcome().decode('utf-8')) # 打印sever欢迎语

# 身份验证
server.user(email)
server.pass_(password)

# stat()返回邮件数量和占用空间:
print('Messages: %s. Size: %s' % server.stat())

# list()返回所有邮件的编号,注意索引号从1开始:
resp, mails, octets = server.list()
# 可以查看返回的列表类似[b'1 82923', b'2 2184', ...]

# 使用retr获取最新一封邮件:
index = len(mails)
resp, lines, octets = server.retr(index)

# lines存储了邮件的原始文本的每一行,
# 可以获得整个邮件的原始文本:
msg_content = b'\r\n'.join(lines).decode('utf-8')

# 稍后解析出邮件的message对象:
msg=Parser().parsestr(msg_content)

# 可以根据邮件索引号直接从服务器删除邮件,此功能需在邮件服务商POP3中开启:
#server.dele(index)

# 关闭连接:
server.quit()

# decode Subject或者地址名中编译过的str
def decode_str(s):
    value, charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value

# 邮件内容的str也需要decode,还需要检测编码,非utf-8的邮件都无法正常显示
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

# 递归打印msg对象, 根据缩进排列内容
def 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()): # 检测邮件中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('%sAttachement: %s' % ('   ' * indent, content_type))

# 处理message对象
print_info(msg)

在这里插入图片描述
在这里插入图片描述

其他材料:
email — 电子邮件与 MIME 处理包 — Python 3.10.4 文档

python email模块 - -零 - 博客园 (cnblogs.com)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值