大家平时在邮箱网页里填写收件人、主题和正文然后点击发送。今天我们用 Python 完成相同的事情。网页里的“发送”按钮背后也需要把邮件交给邮件服务器。Python 程序相当于我们自己编写的一个简易邮件客户端。邮件中的概念寄信类比示例邮件正文信纸上的内容“你好这是课堂实验邮件”邮件头信件上的信息发件人、收件人、主题邮件地址寄件人或收件人地址你的账号163.comSMTP 服务器接收寄件的邮局smtp.163.com端口邮局办理某项业务的窗口465身份认证证明自己有权使用这个寄件账号邮箱地址和授权码SMTP寄送邮件时共同遵守的规则简单邮件传输协议Python 程序││ 登录并提交邮件▼163 的 SMTP 服务器││ 转交邮件▼收件方邮件服务器例如 QQ 邮箱服务器│▼收件人通过网页或客户端查看邮件问使用 163 邮箱给 QQ 邮箱发送邮件Python 应该连接 163 还是 QQ 的 SMTP 服务器答连接发件方的 163 SMTP 服务器。收件人的邮箱可以是 QQ、Outlook 或其他邮箱跨邮箱投递由邮件服务器处理。注SMTP 负责发送邮件。POP3、IMAP 主要用于读取或同步邮箱邮件。本节课实现发送功能不需要编写收信程序。使用python发送邮件程序只需要做两件事1把邮件写好。2把邮件交给服务器发送。python发送邮件需要的库工具负责什么email包构造邮件内容和邮件头smtplib模块连接 SMTP 服务器、登录、发送ssl模块配置加密连接getpass模块在终端输入授权码时隐藏输入内容这些都是python标准库不必执行pip install 。本节课使用MIMEText。Python 也提供了较新的EmailMessage接口后续课程介绍。第一步现在代码中生成一份邮件不用连接服务器也不用发送。from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr sender your_account163.com receiver receiverexample.com message MIMEText( 你好这是我使用 Python 编写的第一封邮件。, plain, utf-8 ) message[From] formataddr((Python课堂, sender)) message[To] formataddr((收件同学, receiver)) message[Subject] Header(Python邮件发送实验, utf-8) print(message.as_string())代码介绍1.MIMEText()构造文本邮件常用形式MIMEText(_text, _subtypeplain, _charsetNone)参数含义本课填写_text正文内容通常是字符串你好……_subtype文本类型plain_charset文本采用的字符编码utf-8message MIMEText(你好, plain, utf-8)创建一份邮件正文内容是“你好”按纯文本处理采用 UTF-8 编码。邮件有两个常见类型# 纯文本 MIMEText(你好同学, plain, utf-8) # HTML MIMEText(h1你好同学/h1, html, utf-8)plain会把正文当普通文字html允许邮件客户端按 HTML 展示。函数返回的是一个邮件对象因此message并不是普通字符串。参数名开头的下划线属于库的命名。2.message[From]等填写邮件头message[From] ... message[To] ... message[Subject] ...字段含义From邮件中显示的发件人To邮件中显示的收件人Subject邮件主题message像一张已经准备好的邮件表单我们现在填写发件人、收件人和主题。这是一种类似字典的操作方式但message本身是邮件对象。3.formataddr()把昵称与邮箱地址组合起来常用形式formataddr(pair, charsetutf-8)参数含义pair一个二元组依次放显示名称和邮箱地址charset显示名称使用的编码默认 UTF-8formataddr((张老师, your_account163.com)) # 这里有两层括号 formataddr( (张老师, your_account163.com) ) # └──────── 一个元组参数 ──────────┘可以理解为昵称张老师 地址your_account163.com组合成符合邮件头格式的字符串。中文昵称可能被编码邮箱客户端会负责解码显示。4.Header()处理中文主题Header(s, charset)参数含义示例s邮件头中的文字Python邮件发送实验charset文字编码utf-8例如message[Subject] Header(Python邮件发送实验, utf-8)正文有正文的编码主题也需要正确处理中文。Header帮我们构造邮件头中的文字。5.as_string()把邮件对象转换成可传输的文本mail_text message.as_string()邮件对象---- as_string()------包含邮件头、正文及编码信息的完整邮件字符串观察print(message.as_string())的结果。看到编码后的字符时这不一定是乱码而可能是邮件传输格式邮箱客户端会解码展示。这个方法只负责转换格式不会发送邮件。二.连接、登录和发送执行顺序建立连接 → 验证身份 → 提交邮件 → 关闭连接1.SMTP_SSL()建立加密连接server smtplib.SMTP_SSL( hostsmtp.163.com, port465, timeout20, contextssl.create_default_context() )参数含义本课取值host发件服务器地址smtp.163.comport服务器端口使用整数465timeout阻塞网络操作的超时时间单位秒20contextTLS 配置对象ssl.create_default_context()local_hostname向服务器介绍本机时使用的名称省略source_address指定本机连接使用的地址和端口省略timeout20不是“20 秒后发送”也不是整个程序必须在 20 秒内结束。Python 等待服务器响应的时候如果连续 20 秒没有收到任何数据就直接抛出超时异常不再死等。服务器地址不要写成https://smtp.163.com 。这里不需要网页协议前缀2.ssl.create_default_context()创建加密配置返回一个 TLS 配置对象用于采用合理的默认设置并验证服务器证书。这个对象告诉 Python建立连接时怎样加密以及怎样确认连接的是可信服务器。3.login()验证发件人身份server.login(user, password)参数本课填写user完整的 163 邮箱地址password163 邮箱生成的客户端授权码4.sendmail()提交邮件server.sendmail( from_addr, to_addrs, msg, mail_options(), rcpt_options() )参数含义示例from_addr实际投递使用的发件地址senderto_addrs实际收件地址课堂统一用列表[receiver]msg完整邮件字符串或字节串message.as_string()mail_options扩展发送选项省略rcpt_options扩展收件选项省略示例server.sendmail(sender, [receiver], message.as_string())receiver # 一个字符串[receiver] # 包含一个字符串的列表该方法返回“被拒绝收件人”的字典。返回{}表示本次提交的收件地址全部被服务器接受全部收件人被拒绝时会抛出异常。5.quit()结束连接server.quit()无需传参数用于结束 SMTP 会话。最终示例使用with smtplib.SMTP_SSL(...) as server: ...退出with代码块时会自动执行退出和连接清理因此不必再手动调用quit()。重点区分两点邮件头与投递地址# 这是邮件里显示的收件人信息。 message[To] formataddr((收件同学, receiver)) # 这里的 [receiver] 才是本次实际投递的收件地址。 server.sendmail(sender, [receiver], message.as_string())课堂实验保持两者一致。可以用“信纸上写的收件人”和“投递时使用的信封地址”帮助区分。三.实验前准备开启 163 的 SMTP 服务准备一个能正常登录的普通 163.com 邮箱。一个真实的收件邮箱例如自己的 QQ 邮箱。Python 3 和可联网的电脑。在 163 邮箱网页版中从“设置”进入“POP3/SMTP/IMAP”的页面开启包含 SMTP 的服务并按照页面要求完成身份验证、生成客户端授权码。配置项目填写内容发件服务器smtp.163.com端口465连接方式SSL/TLS登录账号完整的 163 邮箱地址登录凭证客户端授权码强调三点授权码由发件人的 163 邮箱生成。收件人不需要提供密码或授权码也不必为了收这封邮件开启 SMTP。授权码不要写进课件、作业截图或提交的源码中。授权码不要让别人知道。四、最终实验163 邮箱给其他邮箱发送邮件import smtplib import ssl from getpass import getpass from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr # 1. 获取发件人、收件人和授权码 sender input(请输入发件人的163邮箱).strip() receiver input(请输入收件邮箱例如你的QQ邮箱).strip() auth_code getpass(请输入163邮箱客户端授权码).strip() # 2. 编写正文 body 你好 这是我在 Python 课堂上发送的一封实验邮件。 本次实验中我学习了 1. 构造邮件正文和邮件头。 2. 连接163邮箱的SMTP服务器。 3. 使用授权码登录并发送邮件。 祝学习顺利 # 3. 构造邮件对象 message MIMEText(body, plain, utf-8) message[From] formataddr((Python课堂, sender)) message[To] formataddr((收件同学, receiver)) message[Subject] Header(Python课堂邮件实验, utf-8) # 4. 创建TLS配置 context ssl.create_default_context() # 5. 连接、登录、发送 try: with smtplib.SMTP_SSL( hostsmtp.163.com, port465, timeout20, contextcontext ) as server: server.login(sender, auth_code) refused server.sendmail( sender, [receiver], message.as_string() ) if not refused: print(163服务器已接受邮件请到收件邮箱检查。) else: print(以下收件地址被拒绝, refused) except smtplib.SMTPAuthenticationError as error: print(登录失败请检查163邮箱、授权码及SMTP服务状态。) print(详细信息, error) except smtplib.SMTPRecipientsRefused as error: print(收件地址被拒绝请检查收件邮箱是否填写正确。) print(详细信息, error) except smtplib.SMTPException as error: print(SMTP通信发生异常, error) except OSError as error: print(网络连接或TLS连接发生异常, error)getpass()在普通终端中输入时通常不会显示字符或星号这是正常现象。输入完毕按回车建议在终端运行部分 IDE 输出窗口不支持隐藏输入。写法解释input(提示文字)显示提示读取一行输入返回字符串.strip()本课不传参数去掉输入首尾的空白getpass(提示文字)提示输入授权码通常隐藏输入返回字符串多行文字定义跨越多行的字符串print(...)把一个或多个值显示到终端try尝试执行可能失败的代码except ... as error捕获指定类型的异常用error保存异常信息with ... as server创建并管理连接将连接对象命名为serverif not refused当返回的字典为空时进入该分支这里的try、except、with、if是语法关键字不是函数不需要寻找它们的“函数参数”。实验结果要检查两层程序层面163 服务器是否接受邮件。收件层面收件邮箱是否实际收到必要时查看垃圾邮件文件夹。服务器接受邮件不等于保证立即进入对方收件箱。如果程序已经显示“已接受”随后退出连接时报错也应先检查收件情况避免重复发送。import smtplib import ssl from getpass import getpass from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr # 1. 获取发件人、收件人和授权码 sender 发送邮箱 receiver [接收邮箱] auth_code 授权码 # 2. 编写正文 body 你好 这是我在 Python 课堂上发送的一封实验邮件。 本次实验中我学习了 1. 构造邮件正文和邮件头。 2. 连接163邮箱的SMTP服务器。 3. 使用授权码登录并发送邮件。 祝学习顺利 # 3. 构造邮件对象 message MIMEText(body, plain, utf-8) message[From] formataddr((Python课堂, sender)) message[To] formataddr((收件同学, receiver[0])) message[Subject] Header(Python课堂邮件实验, utf-8) # 4. 创建TLS配置 context ssl.create_default_context() # 5. 连接、登录、发送 try: with smtplib.SMTP_SSL( hostsmtp.163.com, port465, timeout20, contextcontext ) as server: server.login(sender, auth_code) refused server.sendmail( sender, receiver, message.as_string() ) if not refused: print(163服务器已接受邮件请到收件邮箱检查。) else: print(以下收件地址被拒绝, refused) except smtplib.SMTPAuthenticationError as error: print(登录失败请检查163邮箱、授权码及SMTP服务状态。) print(详细信息, error) except smtplib.SMTPRecipientsRefused as error: print(收件地址被拒绝请检查收件邮箱是否填写正确。) print(详细信息, error) except smtplib.SMTPException as error: print(SMTP通信发生异常, error) except OSError as error: print(网络连接或TLS连接发生异常, error)