01. 邮件相关的基础知识

1.1 SMTP/SMTPS协议

  • SMTP协议(Port: 25):简单邮件传输协议,是一个基于文本的协议。在其上指定了一个消息的一个或多个接收者(收件人和抄送人),然后消息文本会被传输。
    • 可以使用简单的Telnet程序来测试一个SMTP服务器。
    • 如今绝大多数邮件服务器都使用该协议。当你给别人发邮件时,你的服务器的某个动态端口(大于1024)就会和邮件服务器的25端口建立一个连接,你发送的邮件会通过这个连接传送到邮件服务器上,保存起来。
    • 需要注意的是,现在国内外的大云主机商(例如:阿里云),多数不允许连接外网的25端口。
  • SMTPS(SMTP-over-SSL)协议:是SMTP协议基于SSL安全协议之上的一种变种协议,它继承了SSL安全协议的非对称加密的高度安全可靠性,可防止邮件泄露。

    • SMTPS和SMTP协议一样,也是用来发送邮件的,只是更安全些,防止邮件被黑客截取泄密,还可实现邮件发送者抗抵赖功能,防止发送者发送之后删除已发邮件,拒不承认发送过这样一份邮件。
    • 端口465和587便是基于SMTPS协议开放的。
      • 465端口(SMTPS):它是SMTPS协议服务所使用的其中一个端口,它在邮件的传输过程中是加密传输(SSL/TLS)的,相比于SMTP协议攻击者无法获得邮件内容,邮件在一开始就被保护了起来。
      • 587端口(SMTPS):它是SMTPS协议服务所使用的另一个端口,它在邮件的传输过程中是加密传输(STARTTLS)的,相比于SMTP协议攻击者无法获得邮件内容,邮件在STARTTLS命令执行后才被加密。
      • 465用于Outlook上,而587可以用于任何程序上。

        1.2 Python对于邮件的支持

  • smtplib:用于登录邮件发送平台。(需要获取邮件平台的SMTP协议支持以及授权码)

  • email:用于构建邮件相关的信息。

    02. Python实现邮件发送

    2.1 获取SMTP协议支持以及授权码

    2.1.1 QQ邮箱版

  • 访问QQ邮箱页面:https://mail.qq.com/,并完成登录。

  • 接着点击右上角的设置。

image.png

  • 接着点击账户,找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务栏,并点击开启POP3/SMTP协议。

image.png

  • 根据提示要求完成相应的验证。

image.png

  • 完成验证后,会自动开启SMTP协议,并获得授权码(打码区域)。(若授权码丢失则重启一下POP3/SMTP协议即可)

image.png

  • 注:点击协议描述文字后面的(如何使用Foxmail等软件收发邮件?)链接,可以查看到POP3服务器与SMTP服务器的域名。
    • POP服务器:pop.qq.com、SMTP服务器:smtp.qq.com。

image.png

2.1.2 网易163版

  • 访问163邮箱页面:https://mail.163.com/,并完成登录。
  • 接着点击右上角的设置 >> POP3/SMTP/IMAP。

image.png

  • 找到POP3/SMTP/IMAP栏,并点击开启POP3/SMTP协议。

image.png

  • 根据提示要求完成相应的验证。

image.png

  • 完成验证后,会自动开启SMTP协议,并获得授权码(打码区域)。(若授权码丢失则重启一下POP3/SMTP协议即可)

image.png

  • 对于163邮箱而言,在该页面的底部的提示栏中可以直接看到相关的服务器地址。

image.png

2.2 发送简单的邮件

  • 实现逻辑:
    • 首先第一步是构造邮件客户端对象,包括指定邮箱的STMP服务器地址,以及协议的端口号(确定是用SMTP协议还是用SMTPS协议)。
    • 然后用客户端对象进行登录,需要指定邮箱账户以及对应的授权码。
    • 接着就可以构建邮件了,包括邮件的正文信息、邮件的元信息(邮件主题、发件人、收件人)。
    • 邮件构建完成之后,使用邮件客户端对象发送邮件即可。
  • 代码实现: ```python import smtplib from email.mime.text import MIMEText

定义常量

SENDER = ‘850340745@qq.com’ # 发件人 RECEIVER = ‘xxy1224adam@163.com’ # 收件人,可指定为列表

RECEIVERS = [‘xxy1224adam@163.com’, ‘xxy1224adam@126.com’, ‘739638877@qq.com’] # 指定多个收件人

授权账户,登录邮件发送服务器

smtp_obj = smtplib.SMTP( host=”smtp.qq.com”, # 邮箱平台的SMTP服务器地址 port=smtplib.SMTP_PORT # SMTP_PORT = 25;SMTP_SSL_PORT = 465 ) smtp_obj.login( user=”850340745@qq.com”, # 邮箱账户 password=”xduxqujbcknbbcfg” # 这里不是输入密码,而是输入账户的授权码 )

构造邮件

设置邮件正文

message = MIMEText( _text=”Hello SmtpLib.”, # 邮件的文本内容 _subtype=’plain’, # 指定文本类型,其中plain为普通文本,也可以指定为html用于发送超文本 _charset=’utf-8’ # 默认使用GBK,建议手动指定为UTF-8 )

设置邮件基本信息

message[‘Subject’] = ‘FirstMailByPython’ # 设置邮件主题 message[‘From’] = SENDER # 发件人 message[‘To’] = RECEIVER # 收件人。

message[‘To’] = “,”.join(RECEIVERS) # 发送给多人时,需要将列表数据转换成字符串,多个邮箱之间用逗号连接。

打包并发送邮件

smtp_obj.sendmail( from_addr=SENDER, to_addrs=RECEIVER,

  1. # to_addrs=RECEIVERS,
  2. msg=message.as_string() # 将构建好的邮件内容转成字符串即可发送

)

  1. <a name="y3lbG"></a>
  2. ### 2.2 发送带有附件的邮件
  3. - 带有附件的邮件需要由以下三个模块来构造:
  4. - `email.mime.text.MIMEText`:构造邮件中的文本内容。
  5. - `email.mime.application.MIMEApplication`:构造邮件中的附件内容。
  6. - `email.mime.multipart.MIMEMultipart`:构建总邮件(将文本内容和邮件内容合并到一起)。
  7. - 基本实现思路:
  8. - 定义发件人和收件人,并授权登录。
  9. - 构造邮件(包括邮件主题、发件人、收件人、邮件文本内容、邮件附件内容)。
  10. - 发送邮件。
  11. - 示例:将路径为`./img/image_1.png`和`./img/image_2.png`的两张图片作为附件发送出去。
  12. ```python
  13. from email.mime.multipart import MIMEMultipart # 总邮件
  14. from email.mime.application import MIMEApplication # 构造附件
  15. from email.mime.text import MIMEText # 构造文本
  16. import smtplib
  17. # 收发件人
  18. SENDER = '850340745@qq.com'
  19. RECEIVER = 'xxy1224adam@163.com'
  20. # 授权登录
  21. smtp_obj = smtplib.SMTP(host="smtp.qq.com", port=smtplib.SMTP_PORT)
  22. smtp_obj.login(user="850340745@qq.com", password="qukqitrdpsrkbcih")
  23. # 构造邮件
  24. ## 设置邮件基本信息
  25. message = MIMEMultipart() # 构造总邮件
  26. message['Subject'] = '带附件的邮件' # 设置邮件主题
  27. message['From'] = SENDER # 发件人
  28. message['To'] = RECEIVER # 收件人
  29. ## 设置邮件文本内容
  30. text = MIMEText(_text="附件是两张图片", _subtype='plain', _charset='utf-8')
  31. message.attach(text) # 将文本内容拼接到总邮件中
  32. ## 设置邮件附件内容
  33. ### 读取附件数据(发送附件的本质是将附件文件的数据发送过去,因此需要先把数据读出来)
  34. with open('./img/image_1.png', 'rb') as file1, open('./img/image_2.png', 'rb') as file2:
  35. img_data1 = file1.read()
  36. img_data2 = file2.read()
  37. ### 构造附件1
  38. enclosure_1 = MIMEApplication(_data=img_data1) # 构造附件
  39. """
  40. add_header()用于构造附件的头部信息,共有三个参数(其中前两个是固定的):
  41. 参数1:固定为字符串'content-disposition',用于描述附件文件的数据处理方式;
  42. 参数2:固定为字符串'attachment',表示附件的内容处理方式为拼接;
  43. 参数3filename:附件的文件名(可与附件的原始文件名不一致)。
  44. """
  45. enclosure_1.add_header('content-disposition', 'attachment', filename='附件图片1.png') # 构造头部信息
  46. message.attach(enclosure_1) # 将附件1拼接到总邮件中
  47. ### 构造附件2
  48. enclosure_2 = MIMEApplication(_data=img_data2)
  49. enclosure_2.add_header('content-disposition', 'attachment', filename='附件图片2.png')
  50. message.attach(enclosure_2)
  51. # 发送邮件
  52. smtp_obj.sendmail(from_addr=SENDER, to_addrs=RECEIVER, msg=message.as_string())

2.3 封装邮件发送功能

  • 若要让一个函数可以发送一封邮件,则这个函数压根具有以下参数:
    • server:定义邮件发送平台的服务器地址。
    • sender:发送者(即用户邮箱地址)。
    • password:账户的授权码。
    • receivers:接收者(因为可能只有一个接收者,也可能有多个接收者,所以receivers即可以是一个字符串,也可以是一个列表)。
    • subject:邮件的主题。
    • content:邮件的文本内容(有些邮件可能只有附件,没有文本内容,故存在默认值''空字符)。
    • file_paths:附件的路径。
      • 有些邮件可能只有文本内容,没有附件,故存在默认值None。
      • 有些邮件可能有多个附件,因此这里传入的应该是一个列表。
  • 函数实现: ```python from email.mime.multipart import MIMEMultipart from email.mime.application import MIMEApplication from email.mime.text import MIMEText import smtplib, os

def send_email(server, sender, password, receivers, subject, content=’’, file_paths=None):

  1. # 构建邮件基本信息
  2. message = MIMEMultipart() # 邮件可能存在附件,为了保证通用性,因此需要构建总邮件。
  3. message['Subject'] = subject # 设置邮件主题
  4. message['From'] = sender # 设置发件人
  5. # 若receivers是一个字符串,代表收件人只有一个;
  6. # 若receivers是个列表,则代表收件人有多个,需要用逗号拼起来。
  7. message['To'] = receivers if type(receivers) == str else ",".join(receivers) # 设置收件人
  8. # 构建文本内容
  9. text = MIMEText(_text=content, _subtype='plain', _charset='utf-8')
  10. message.attach(text)
  11. # 构建附件内容
  12. if file_paths is not None:
  13. # 若file_paths不为None,则说明有附件需要发送。
  14. for file_path in file_paths:
  15. # 跟据路径读取文件数据。
  16. with open(file_path, 'rb') as file:
  17. file_data = file.read()
  18. # 构造附件
  19. enclosure = MIMEApplication(_data=file_data)
  20. enclosure.add_header('content-disposition', 'attachment', filename=os.path.basename(file_path))
  21. # 拼接附件
  22. message.attach(enclosure)
  23. # 登录邮件并发送
  24. smtp_obj = smtplib.SMTP(host=server, port=smtplib.SMTP_PORT)
  25. smtp_obj.login(user=sender, password=password)
  26. smtp_obj.sendmail(from_addr=sender, to_addrs=receivers, msg=message.as_string())

send_email( server=”smtp.qq.com”, sender=”850340745@qq.com”, password=”qukqitrdpsrkbcih”, receivers=[‘xxy1224adam@163.com’, ‘xxy1224adam@126.com’, ‘739638877@qq.com’], subject=”邮件封装测试”, content=”这封邮件有两张图片”, file_paths=[‘./img/image_1.png’, ‘./img/image_2.png’] )

  1. <a name="bWNPg"></a>
  2. ### 2.4 定时邮件
  3. <a name="YJDJY"></a>
  4. #### 2.4.1 定时任务介绍
  5. - Python中可以使用schedule这个第三方模块来完成定时任务的编写,因此需要先安装schedule模块。
  6. ```python
  7. pip install schedule
  • Schedule模块中的一些常用定时: ```python

    封装定时任务要执行的函数

    def job(): print(“I’m working…”)

schedule.every(10).seconds.do(job) # 每10秒种执行job函数 schedule.every(10).minutes.do(job) # 每10分钟执行job函数 schedule.every().hour.do(job) # 每1小时执行job函数 schedule.every().day.at(“10:30”).do(job) # 每天的10:30执行job函数 schedule.every(5).to(10).minutes.do(job) # 每5~10分种执行一次job函数 schedule.every().monday.do(job) # 每个星期一的00:00执行job函数 schedule.every().wednesday.at(“13:15”).do(job) # 每个星期三的13:15执行job函数 schedule.every().minute.at(“:17”).do(job) # 每分钟的第17秒执行job函数

  1. <a name="cnbko"></a>
  2. #### 2.4.2 定时邮件发送
  3. - 示例:每天的09:59发送一封邮件,邮件和2.3中的一致即可。
  4. ```python
  5. import schedule
  6. # do()的第一参数是定时任务函数的函数名
  7. # 后面可以用可变参数将定时任务函数的参数传进去
  8. schedule.every().day.at("09:59").do(
  9. send_email,
  10. server="smtp.qq.com",
  11. sender="850340745@qq.com",
  12. password="qukqitrdpsrkbcih",
  13. receivers=['xxy1224adam@163.com', 'xxy1224adam@126.com', '739638877@qq.com'],
  14. subject="邮件封装测试",
  15. content="这封邮件有两张图片",
  16. file_paths=['./img/image_1.png', './img/image_2.png']
  17. )
  18. # 因为是定时任务,所以程序要一直开着
  19. while True:
  20. schedule.run_pending() # 运行定时任务队列