Python进阶篇(五)-- 邮件客户端实现与电子邮件发送

1 SMTP

        通过完成本实验,我们将更加了解SMTP协议。还将学到使用Python实现标准协议的经验。

        主要任务是开发一个简单的邮件客户端,将邮件发送给任意收件人。客户端将需要连接到邮件服务器,使用SMTP协议与邮件服务器进行对话,并向邮件服务器发送电子邮件。 Python提供了一个名为smtplib的模块,它内置了使用SMTP协议发送邮件的方法。但是我们不会在本实验中使用此模块,因为它隐藏了SMTP和套接字编程的细节。

        为了限制垃圾邮件,一些邮件服务器不接受来源随意的TCP连接。对于下面所述的实验,您可能需要尝试连接到您的大学邮件服务器和流行的Webmail服务器(如AOL邮件服务器)。您也可以尝试从您的家和您的大学校园进行连接。

        这里采用qq邮箱来完成。本文将实现一个SMTP客户端,使用qq邮箱作为发件人,向指定的163邮箱发送一封邮件。

        SMTP协议即简单邮件传输协议,允许用户按照标准发送/接收邮件。

        在本文中,SMTP邮件客户端程序的基本流程如下:

  1. 与qq邮件服务器建立TCP连接,域名"smtp.qq.com",SMTP默认端口号25。建立连接后服务器将返回状态码220,代表服务就绪(类似HTTP,SMTP也使用状态码通知客户端状态信息)。
  2. 发送"HELO"命令,开始与服务器的交互,服务器将返回状态码250(请求动作正确完成)。
  3. 发送"AUTH LOGIN"命令,开始验证身份,服务器将返回状态码334(服务器等待用户输入验证信息)。
  4. 发送经过base64编码的用户名(本例中是163邮箱的账号),服务器将返回状态码334(服务器等待用户输入验证信息)。
  5. 发送经过base64编码的密码(本例中是163邮箱的密码),服务器将返回状态码235(用户验证成功)。
  6. 发送"MAIL FROM"命令,并包含发件人邮箱地址,服务器将返回状态码250(请求动作正确完成)。
  7. 发送"RCPT TO"命令,并包含收件人邮箱地址,服务器将返回状态码250(请求动作正确完成)。
  8. 发送"DATA"命令,表示即将发送邮件内容,服务器将返回状态码354(开始邮件输入,以"."结束)。
  9. 发送邮件内容,服务器将返回状态码250(请求动作正确完成)。
  10. 发送"QUIT"命令,断开与邮件服务器的连接。

1.2 代码实现

# -*- encoding: utf-8 -*-
# @Author: CarpeDiem
# @Date: 230504
# @Version: 1.0
# @Description: Python 实现邮件客户端

from socket import *
import base64

# Mail content
subject = "I love computer networks!"
contenttype = "text/plain"
msg = "I love computer networks!"
endmsg = "\r\n.\r\n"

# Choose a mail server (e.g. Google mail server) and call it mailserver 
mailserver = "smtp.qq.com"

# Sender and reciever
fromaddress = "[email protected]"
toaddress = "[email protected]"

# Auth information (Encode with base64)
username = "[email protected]"
password = "xxxxxxxxxxx"

username = base64.b64encode(username.encode()).decode()
password = base64.b64encode(password.encode()).decode()

# Create socket called clientSocket and establish a TCP connection with mailserver
clientSocket = socket(AF_INET, SOCK_STREAM) 
clientSocket.connect((mailserver, 25))

recv = clientSocket.recv(1024).decode()
print(recv)
if recv[:3] != '220':
    print('220 reply not received from server.')

# Send HELO command and print server response.
heloCommand = 'HELO CarpeDiem\r\n'
clientSocket.send(heloCommand.encode())
recv1 = clientSocket.recv(1024).decode()
print(recv1)
if recv1[:3] != '250':
    print('250 reply not received from server.')

# Auth
clientSocket.sendall('AUTH LOGIN\r\n'.encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '334'):
	print('334 reply not received from server')

clientSocket.sendall((username + '\r\n').encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '334'):
	print('334 reply not received from server')

clientSocket.sendall((password + '\r\n').encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '235'):
	print('235 reply not received from server')

# Send MAIL FROM command and print server response.
clientSocket.sendall(('MAIL FROM: <' + fromaddress + '>\r\n').encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '250'):
	print('250 reply not received from server')

# Send RCPT TO command and print server response.
clientSocket.sendall(('RCPT TO: <' + toaddress + '>\r\n').encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '250'):
	print('250 reply not received from server')

# Send DATA command and print server response.
clientSocket.send('DATA\r\n'.encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '354'):
	print('354 reply not received from server')

# Send message data.
message = 'from:' + fromaddress + '\r\n'
message += 'to:' + toaddress + '\r\n'
message += 'subject:' + subject + '\r\n'
message += 'Content-Type:' + contenttype + '\t\n'
message += '\r\n' + msg
clientSocket.sendall(message.encode())

# Message ends with a single period.
clientSocket.sendall(endmsg.encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '250'):
	print('250 reply not received from server')

# Send QUIT command and get server response.
clientSocket.sendall('QUIT\r\n'.encode())

# Close connection
clientSocket.close()

        温馨提示: 有些邮箱默认关闭SMTP服务,比如本文使用的qq邮箱。需要在设置中打开SMTP服务。另外,qq邮箱在打开SMTP服务后,会设置一个授权码,在程序使用这个授权码作为密码登录,而不是平时使用的密码。

        一切正常的话,运行效果如下图所示,将会看到服务器返回的每条消息,其中包含每次操作后返回的状态码。

        同时,我们还可以登陆发件人邮箱和收件人邮箱,在发件人的已发送文件夹中和收件人的收件箱中都能看到这封被发送的邮件。左图为qq邮箱,右图为网易163邮箱。


2 网络应用开发

2.1 发送电子邮件

        在即时通信软件如此发达的今天,电子邮件仍然是互联网上使用最为广泛的应用之一,公司向应聘者发出录用通知、网站向用户发送一个激活账号的链接、银行向客户推广它们的理财产品等几乎都是通过电子邮件来完成的,而这些任务应该都是由程序自动完成的。

        就像我们可以用HTTP(超文本传输协议)来访问一个网站一样,发送邮件要使用SMTP(简单邮件传输协议),SMTP也是一个建立在TCP(传输控制协议)提供的可靠数据传输服务的基础上的应用级协议,它规定了邮件的发送者如何跟发送邮件的服务器进行通信的细节,而Python中的smtplib模块将这些操作简化成了几个简单的函数。

        smtplib和email,这俩模块是Python自带的,只需import即可使用。smtplib模块主要负责发送邮件,email模块主要负责构造邮件。

  • smtplib模块主要负责发送邮件:是一个发送邮件的动作,连接邮箱服务器,登录邮箱,发送邮件(有发件人,收信人,邮件内容)。
  • email模块主要负责构造邮件:指的是邮箱页面显示的一些构造,如发件人,收件人,主题,正文,附件等。

代码说明:

  1. smtplib模块

smtplib.SMTP()              # 实例化SMTP()

login(user, password)
# user:登录邮箱的用户名。
# password:登录邮箱的密码,像笔者用的是网易邮箱,网易邮箱一般是网页版,需要用到客户端密码,需要在网页版的网易邮箱中设置授权码,该授权码即为客户端密码。

sendmail(from_addr, to_addrs, msg,)
# from_addr:邮件发送者地址
# to_addrs:邮件接收者地址。字符串列表[‘接收地址1’,‘接收地址2’,‘接收地址3’,…]
# msg:发送消息:邮件内容。一般是msg.as_string():as_string()是将msg(MIMEText对象或者MIMEMultipart对象)变为str。

quit()      # 用于结束SMTP会话。

2) email模块

        email模块下有mime包,mime英文全称为“Multipurpose Internet Mail Extensions”,即多用途互联网邮件扩展,是目前互联网电子邮件普遍遵循的邮件技术规范。

        该mime包下常用的有三个模块:text, image, multpart。

        导入方法如下:

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header

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

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

        下面的代码演示了如何在Python发送普通的文字邮件。

1. 发送普通文字邮件

# -*- encoding: utf-8 -*-
# @Author: CarpeDiem
# @Date: 230428
# @Version: 1.0
# @Description: Python 发送邮件

from smtplib import SMTP_SSL
from email.header import Header 
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def main():
    # 设定邮件发送者和接受者
    host_server = 'smtp.qq.com'             # qq 邮箱smtp服务器
    sender = '[email protected]'              # 发件人邮箱
    pwd = 'xxxxxxxxxxxxx'
    receivers = ['[email protected]', '[email protected]']              # 收件人邮箱
    mail_title = "Python自动发送的邮件"                               # 邮件标题  
    mail_content = "您好,这是使用python登录QQ邮箱发送邮件的测试——xq"   # 邮件正文内容

    message = MIMEMultipart()               # 初始化一个邮件主体
    message['Subject'] = Header(mail_title, 'utf-8')
    message['From'] = sender
    message['To'] = ";".join(receivers)
    message.attach(MIMEText(mail_content, 'plain', 'utf-8'))    # 邮件正文内容

    smtper = SMTP_SSL(host_server)      # ssl登录
    # login(user,password):
    # user:登录邮箱的用户名。
    # password:登录邮箱的密码,这里用的是QQ邮箱,
    # 需要用到客户端密码,需要在QQ邮箱中设置授权码,该授权码即为客户端密码
    smtper.login(sender, pwd)
    smtper.sendmail(sender, receivers, message.as_bytes())
    print("邮件发送完成!")
    # quit(): 用于结束SMTP会话
    smtper.quit()

if __name__ == '__main__':
    main()

2. 发送html格式邮件

# -*- encoding: utf-8 -*-
# @Author: CarpeDiem
# @Date: 230419
# @Version: 1.0
# @Description: Python 发送HTML格式邮件

import smtplib
from smtplib import SMTP_SSL
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header

host_server = "smtp.qq.com"             # qq邮箱smtp服务器
sender_qq = "[email protected]"         # 发件人邮箱
password = "xxxxxxxxxxxx"           # 授权码
receiver = "[email protected]"
mail_title = "Python自动发送html格式的邮件" # 邮件标题
# 邮件正文内容
mail_content = "您好!<p>这是使用python登录QQ邮箱发送\
                HTNL格式邮件的测试:</p> <p>\
                <a href='https://blog.csdn.net/xq151750111?spm=1010.2135.3001.5421'>CSDN个人主页</a></p>"

msg = MIMEMultipart()
msg["Subject"] = Header(mail_title, "utf-8")
msg["From"] = sender_qq
msg["To"] = Header("测试邮箱", "utf-8")

msg.attach(MIMEText(mail_content, 'html'))

try:
    smtp = SMTP_SSL(host_server)    # ssl登录连接到邮件服务器
    smtp.set_debuglevel(True)       # False to disable debug
    smtp.ehlo(host_server)          # 跟服务器打招呼,告诉它我们准备连接
    smtp.login(sender_qq, password)
    smtp.sendmail(sender_qq, receiver, msg.as_string())
    smtp.quit()
    print("邮件发送成功")
except smtplib.SMTPException:
    print("无法发送邮件")

3. 发送带附件的邮件

# -*- encoding: utf-8 -*-
# @Author: CarpeDiem
# @Date: 230419
# @Version: 1.0
# @Description: Python 发送HTML格式邮件以及附件

import string
import smtplib
from smtplib import SMTP_SSL
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.application import MIMEApplication  # 用于添加附件

host_server = "smtp.qq.com"             # qq邮箱smtp服务器
sender_qq = "[email protected]"         # 发件人邮箱
password = "xxxxxxxxxxxx"           # 授权码
receiver = "[email protected]"
mail_title = "Python自动发送html格式的邮件" # 邮件标题
# 邮件正文内容
mail_content = "您好!<p>这是使用python登录QQ邮箱发送\
                HTNL格式邮件的测试:</p> <p>\
                <a href='https://blog.csdn.net/xq151750111?spm=1010.2135.3001.5421'>CSDN个人主页</a></p>"

msg = MIMEMultipart()
msg["Subject"] = Header(mail_title, "utf-8")
msg["From"] = sender_qq
msg["To"] = Header("测试邮箱", "utf-8")

msg.attach(MIMEText(mail_content, 'html'))
attachment = MIMEApplication(open("H:\\毕业设计\\LassoNet\\脑区选择.xlsx", 'rb').read())
attachment["Content-Type"] = "application/octet-stream"

# 给附件重命名
basename = "test.xlsx"
attachment.add_header('Content-Disposition', 'attachment', filename=('utf-8', '', basename))
msg.attach(attachment)

try:
    smtp = SMTP_SSL(host_server)    # ssl登录连接到邮件服务器
    smtp.set_debuglevel(True)       # False to disable debug
    smtp.ehlo(host_server)          # 跟服务器打招呼,告诉它我们准备连接
    smtp.login(sender_qq, password)
    smtp.sendmail(sender_qq, receiver, msg.as_string())
    smtp.quit()
    print("邮件发送成功")
except smtplib.SMTPException:
    print("无法发送邮件")

2.2 发送短信

        发送短信也是项目中常见的功能,网站的注册码、验证码、营销信息基本上都是通过短信来发送给用户的。在下面的代码中我们使用了互亿无线短信平台(该平台为注册用户提供了50条免费短信以及常用开发语言发送短信的demo,可以登录该网站并在用户自服务页面中对短信进行配置)提供的API接口实现了发送短信的服务,当然国内的短信平台很多,读者可以根据自己的需要进行选择(通常会考虑费用预算、短信达到率、使用的难易程度等指标),如果需要在商业项目中使用短信服务建议购买短信平台提供的套餐服务。

import urllib.parse
import http.client
import json


def main():
    host  = "106.ihuyi.com"
    sms_send_uri = "/webservice/sms.php?method=Submit"
    # 下面的参数需要填入自己注册的账号和对应的密码
    params = urllib.parse.urlencode({
    
    'account': 'API ID', 'password' : 'API KEY', 'content': '您的验证码是:666888。请不要把验证码泄露给其他人。', 'mobile': 'xxxxxxxxx', 'format':'json' })
    print(params)
    headers = {
    
    'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'}
    conn = http.client.HTTPConnection(host, port=80, timeout=30)
    conn.request('POST', sms_send_uri, params, headers)
    response = conn.getresponse()
    response_str = response.read()
    jsonstr = response_str.decode('utf-8')
    print(json.loads(jsonstr))
    conn.close()


if __name__ == '__main__':
    main()

        详细了解,请阅读:短信验证码/通知 - API文档


参考

猜你喜欢

转载自blog.csdn.net/xq151750111/article/details/130495658
今日推荐