Rumah > Artikel > pembangunan bahagian belakang > Cara menggunakan modul e-mel, smtplib, poplib, imaplib untuk menghantar dan menerima e-mel dalam Python
Perjalanan e-mel ialah:
Ejen Pengguna Mel (MUA) merujuk kepada klien e-mel atau perisian yang digunakan oleh pengguna untuk mengakses akaun e-mel mereka. (iaitu perisian e-mel yang serupa dengan Outlook)
MTA: Ejen Pemindahan Mel-Ejen Pemindahan Mel, iaitu pembekal perkhidmatan E-mel, seperti NetEase, Sina, dsb.
MDA: Ejen Penghantaran Mel——Ejen penghantaran mel. Pelayan pembekal perkhidmatan E-mel
Penghantar-> MTA ->
perlu menulis program untuk menghantar dan menerima e-mel, pada asasnya:
Tulis MUA untuk menghantar e-mel ke MTA
Tulis MUA untuk menerima e-mel daripada MDA.
Apabila menghantar e-mel, protokol yang digunakan oleh MUA dan MTA ialah SMTP: Simple Mail Transfer Protocol MTA seterusnya juga menggunakan protokol SMTP ke MTA yang lain.
Apabila menerima e-mel, MUA dan MDA menggunakan dua protokol: POP: Post Office Protocol, versi semasa ialah 3, biasanya dikenali sebagai POP3: Internet Message Access Protocol, versi semasa ialah 4, kelebihannya ialah yang bukan sahaja boleh Mel, anda juga boleh mengendalikan terus mel yang disimpan pada MDA, seperti mengalihkannya dari peti masuk ke tong sampah, dsb.
Simple Mail Transfer Protocol Ia adalah satu set peraturan untuk menghantar mel dari alamat sumber ke alamat destinasi, yang mengawal aliran surat mod transit.
Smtplib Python menyediakan cara yang sangat mudah untuk menghantar e-mel. Ia hanya merangkum protokol SMTP.
Python mempunyai dua modul, smtplib
dan email
, untuk sokongan SMTP email
bertanggungjawab untuk membina e-mel dan smtplib
bertanggungjawab untuk menghantar e-mel.
Membina objek e-mel ialah objek Messag
, ia mewakili objek e-mel teks anda membina objek MIMEText
mewakili gambar sebagai lampiran Untuk menggabungkan berbilang objek, gunakan objek MIMEImage
dan MIMEMultipart
boleh mewakili sebarang objek. Hubungan warisan mereka adalah seperti berikut: MIMEBase
Message +- MIMEBase +- MIMEMultipart +- MIMENonMultipart +- MIMEMessage +- MIMEText +- MIMEImageMula-mula, kami membina e-mel teks biasa yang paling mudah, dan kemudian menghantarnya melalui SMTP.
from email.mime.text import MIMEText msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')menyedari bahawa apabila membina objek
, parameter pertama ialah badan e-mel, dan parameter kedua ialah subjenis MIME Pass dalam MIMEText
, dan MIME terakhir ialah 'plain'
. Parameter terakhir mesti Dikodkan dengan 'text/plain'
untuk memastikan keserasian berbilang bahasa. utf-8
import smtplib smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )Penerangan parameter:
SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options])Penerangan parameter:
Kaedahnya adalah untuk menghantar e-mel Memandangkan ia boleh dihantar kepada berbilang orang pada satu masa, sendmail()
dihantar masuk. Isi e-mel ialah list
> menukar objek str
menjadi as_string()
. MIMEText
str
Teks yang dikodkan oleh objek
Header
Berikut ialah contoh mudah menggunakan Python untuk menghantar e-mel:
import smtplib from email.mime.text import MIMEText from email.header import Header sender = 'from@runoob.com' receivers = ['429240967@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 # 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码 message = MIMEText('Python 邮件发送测试内容...', 'plain', 'utf-8') message['From'] = Header("菜鸟教程", 'utf-8') # 发送者 message['To'] = Header("测试", 'utf-8') # 接收者 message['Subject'] = Header('Python SMTP 邮件测试主题', 'utf-8') try: smtpObj = smtplib.SMTP('localhost') smtpObj.sendmail(sender, receivers, message.as_string()) print "邮件发送成功" except smtplib.SMTPException: print "Error: 无法发送邮件"
2 Gunakan perkhidmatan SMTP pihak ketiga
login()
Nama penerima tidak dipaparkan sebagai nama mesra, seperti penggunaan
; kaedah formataddr untuk Memformat alamat e-mel. Jika ia mengandungi bahasa Cina, ia perlu dikodkan melalui objek Mr Green
.
Header
menerima rentetan dan bukannya senarai Jika terdapat berbilang alamat e-mel, pisahkan sahaja dengan
import smtplib from email.mime.text import MIMEText from email.utils import formataddr # 第三方 SMTP 服务 mail_host = "mail.sss.com" # 设置服务器 mail_user = "it_system@sss.com" # 用户名 mail_pass = "Ssss201709#" # 口令 sender = 'it_system@tcl.com' receivers = 'sss.yang@tcsssl.com' # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 message = MIMEText('Python 邮件内容测试...', 'plain', 'utf-8') message['From'] = formataddr(('SCBC-啊iT', sender)) message['To'] = formataddr(('杨生', receivers)) message['Subject'] = 'Python SMTP 邮件测试' try: smtpObj = smtplib.SMTP() smtpObj.connect(mail_host, 25) # 25 为 SMTP 端口号 smtpObj.login(mail_user, mail_pass) smtpObj.sendmail(sender, receivers, message.as_string()) print("邮件发送成功") except smtplib.SMTPException: print("Error: 无法发送邮件")
Python在构造MIMEText
对象时,把HTML字符串传进去,再把第二个参数由plain
变为html
就可以了:
具体代码如下:
mail_msg = """ Python 邮件内容测试... 这是一个链接 """ message = MIMEText(mail_msg, 'html', 'utf-8')
带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个MIMEMultipart
对象代表邮件本身,然后往里面加上一个MIMEText
作为邮件正文,再继续往里面加上表示附件的MIMEBase
对象即可。
发送带附件的邮件,首先要创建MIMEMultipart()实例,然后构造附件,如果有多个附件,可依次构造,最后利用smtplib.smtp发送。
from email.mime.multipart import MIMEMultipart # 创建一个带附件的实例 message = MIMEMultipart() message['From'] = formataddr(('SCBC-啊iT', sender)) message['To'] = formataddr(('杨生', receivers)) message['Subject'] = 'Python SMTP 邮件测试' mail_msg = """ Python 邮件内容测试... 这是一个链接 """ # 邮件正文内容 message.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 构造附件1,传送当前目录下的 test.txt 文件 att = MIMEText(open('32.txt', 'rb').read(), 'base64', 'utf-8') att["Content-Type"] = 'application/octet-stream' # 这里的filename可以任意写,写什么名字,邮件中显示什么名字 att["Content-Disposition"] = 'attachment; filename="32.txt"' message.attach(att)
要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,然后,在HTML中通过引用src="cid:0"
就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的cid:x
即可。
邮件的 HTML 文本中一般邮件服务商添加外链是无效的,正确添加图片的实例如下所示:
from email.mime.multipart import MIMEMultipart from email.mime.image import MIMEImage # 创建一个带附件的实例 message = MIMEMultipart() message['From'] = formataddr(('SCBC-啊iT', sender)) message['To'] = formataddr(('杨生', receivers)) message['Subject'] = 'Python SMTP 邮件测试' mail_msg = """ Python 邮件内容测试... 这是一个链接 图片演示: """ # 邮件正文内容 message.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 指定图片为当前目录 with open('a.jpg', 'rb') as fp: msgImage = MIMEImage(fp.read()) # 定义图片 ID,在 HTML 文本中引用 msgImage.add_header('Content-ID', '') message.attach(msgImage)
或者通过MIMEBase来添加图片
# 指定图片为当前目录 with open('a.jpg', 'rb') as fp: # 设置附件的MIME和文件名,这里是png类型: mime = MIMEBase('image', 'jpg', filename='a.jpg') # 加上必要的头信息: mime.add_header('Content-Disposition', 'attachment', filename='附件显示名称.jpg') mime.add_header('Content-ID', '') # 如果有多个文件需要使用.format(index) mime.add_header('X-Attachment-Id', '0') # 如果有多个文件需要使用.format(index) # 把附件的内容读进来: mime.set_payload(fp.read()) # 用Base64编码: encoders.encode_base64(mime) # 添加到MIMEMultipart: message.attach(mime)
如果我们发送HTML邮件,收件人通过浏览器或者Outlook之类的软件是可以正常浏览邮件内容的,但是,如果收件人使用的设备太古老,查看不了HTML邮件怎么办?
办法是在发送HTML的同时再附加一个纯文本,如果收件人无法查看HTML格式的邮件,就可以自动降级查看纯文本邮件。
利用MIMEMultipart
就可以组合一个HTML和Plain,要注意指定subtype是alternative
:
msg = MIMEMultipart('alternative') msg['From'] = ... msg['To'] = ... msg['Subject'] = ... msg.attach(MIMEText('hello', 'plain', 'utf-8')) msg.attach(MIMEText('Hello', 'html', 'utf-8')) # 正常发送msg对象...
如果使用标准的25端口连接SMTP服务器发送邮件,即使身份验证成功,因为明文传输的缘故,整个邮件发送过程都有可能被窃听。要更安全地发送邮件,可以加密SMTP会话,实际上就是先创建SSL安全连接,然后再使用SMTP协议发送邮件。
在某些邮件服务提供商(例如Gmail)中,SMTP服务必须使用加密传输。我们来看看如何通过Gmail提供的安全SMTP发送邮件。
只需要在创建SMTP
对象后,立刻调用starttls()
方法,就创建了安全连接。后面的代码和前面的发送邮件代码完全一样。
必须知道,Gmail的SMTP端口是587,因此,修改代码如下:
smtp_server = 'smtp.gmail.com' smtp_port = 587 server = smtplib.SMTP(smtp_server, smtp_port) server.starttls() # 剩下的代码和前面的一模一样: server.set_debuglevel(1)
收取邮件就是编写一个MUA作为客户端,从MDA把邮件获取到用户的电脑或者手机上。收取邮件最常用的协议是POP协议,目前版本号是3,俗称POP3。
Python内置一个poplib
模块,实现了POP3协议,可以直接用来收邮件。
POP3 的命令和响应数据都是基于 ASCII 文本的,并以 CR 和 LF(/r/n) 作为行结束符,响应数据包括一个表示返回状态的符号(+/)和描述信息。
请求和响应的标准格式如下:
请求标准格式:命令 [参数] CRLF
响应标准格式:+OK /[-ERR] description CRLF
POP3 协议客户端的命令和服务器端对应的响应数据如下:
user name:向 POP 服务器发送登录的用户名。
pass string:向 POP 服务器发送登录的密码。
quit:退出 POP 服务器。
stat:统计邮件服务器状态,包括邮件数和总大小。
list [msg_no]:列出全部邮件或指定邮件。返回邮件编号和对应大小。
retr msg_no:获取指定邮件的内容(根据邮件编号来获取,编号从 1 开始)。
dele msg_no:删除指定邮件(根据邮件编号来删除,编号从 1 开始)。
noop:空操作。仅用于与服务器保持连接。
rset:用于撤销 dele 命令。
poplib 模块完全模拟了上面命令,poplib.POP3 或 poplib.POP3_SSL 为上面命令提供了相应的方法,开发者只要依次使用上面命令即可从服务器端下载对应的邮件
注意到POP3协议收取的不是一个已经可以阅读的邮件本身,而是邮件的原始文本,这和SMTP协议很像,SMTP发送的也是经过编码后的一大段文本。
要把POP3收取的文本变成可以阅读的邮件,还需要用email
模块提供的各种类来解析原始文本,变成可阅读的邮件对象。
所以,收取邮件分两步:
用POP3获取邮件其实很简单,要获取所有邮件,只需要循环使用retr()
把每一封邮件内容拿到即可。真正麻烦的是把邮件的原始内容解析为可以阅读的邮件对象。
import poplib from email.parser import Parser # email.parser 解析电子邮件,返回这个对象的email.message.Message实例 from email.header import decode_header from email.utils import parseaddr # 服务器及用户信息 host = 'mail.tcl.com' username = 'bobin.yang@tcl.com' password = 'Ybb7654321' # 连接到POP3服务器 conn = poplib.POP3_SSL(host) # 注意qq邮箱使用SSL连接 # 设置调试模式,可以看到与服务器的交互信息 conn.set_debuglevel(1) # 打印POP3服务器的欢迎文字 print(conn.getwelcome().decode("utf-8")) # 身份认证 conn.user(username) conn.pass_(password) # 获取服务器上信件信息,返回一个列表,第一项是一共有多少封邮件,第二项是共有多少字节 # stat()返回邮件数量和占用空间 mail_total, total_size = conn.stat() print('message: %s.Size:%s' % (mail_total, total_size)) # list()返回(response, ['mesg_num octets', ...], octets),第二项是编号 resp, mails, octets = conn.list() print(mails) # 返回的列表类似[b'1 82923', b'2 2184', ...] # 获取最新一封邮件,注意索引号从1开始 # POP3.retr(which) 检索序号which的这个邮件,然后设置他的出现标志 返回(response, ['line', ...], octets)这个三元组 resp, lines, ocetes = conn.retr(len(mails)) print('lines:', len(lines)) # lines 存储了邮件的原始文本的每一行 # 可以获得整个邮件的原始文本 print("-------------------")
消息
对象,然后,用适当的形式把邮件内容展示给用户即可。解析邮件的过程和上一节构造邮件正好相反。
程序在创建 BytesParser(解析字节串格式的邮件数据)或 Parser(解析字符串格式的邮件数据)时,必须指定 policy=default
;否则,BytesParse 或 Parser 解析邮件数据得到的就是过时的 Message 对象,,不是新的 EmailMessage,处理起来非常不方便。
msg = b'\r\n'.join(lines).decode('utf-8') # 解析出邮件 msg = Parser().parsestr(msg) # email.Parser.parsestr(text, headersonly=False) # 与parser()方法类似,不同的是他接受一个字符串对象而不是一个类似文件的对象 # 可选的headersonly表示是否在解析玩标题后停止解析,默认为否 # 返回根消息对象 # 编码处理,文本邮件的内容也是str,还需要检测编码,否则,非UTF-8编码的邮件都无法正常显示 def guess_charset(msg): charset = msg.get_charset() # 从msg对象获取编码 if charset is None: content_type = msg.get('Content-Type', '').lower() # 如果获取不到,再从content—type字段获取 if 'charset' in content_type: charset = content_type.split('charset=')[1].strip() return charset return charset # 数据解码,邮件的Subject或者Email中包含的名字都是经过编码后的str,要正常显示,就必须decode def decode_str(s): value, charset = decode_header(s)[0] # 数据,数据编码方式,from email.header import decode_header # decode_header()返回一个list,因为像Cc、Bcc这样的字段可能包含多个邮件地址,所以解析出来的会有多个元素。上面的代码我们偷了个懒,只取了第一个元素。 if charset: value = value.decode(charset) return value # print_ingo函数:解析邮件与构造邮件的步骤正好相反 def print_info(msg, indent=0): # indent用于缩进显示 if indent == 0: for header in ['From', 'To', 'Subject']: # 邮件的from、to、subject存在于根对象上 value = msg.get(header, '') if value: if header == 'Subject': value = decode_str(value) # 需要解码subject字符串 else: # 解码mail地址 hdr, addr = parseaddr(value) name = decode_str(hdr) value = '%s' % addr print('%s: %s %s' % (header, value, name)) print('-*-' * 20) if msg.is_multipart(): # 如果邮件对象是一个is_multipart,get_payload()返回一个list,包含所有子对象 parts = msg.get_payload() # 循环获得列表项 for n, part in enumerate(parts): # print('%spart %s' % (' ' * indent, n)) # print('%s------------' % (' ' * indent)) # 递归打印没一个子对象 print_info(part, indent + 1) else: # 邮件对象不是一个is_multipart,就根据content_type判断 content_type = msg.get_content_type() # 数据类型 if content_type == 'text/plain' or content_type == 'text/html': # 纯文本 html文本 # 纯文本或html内容 content = msg.get_payload(decode=True) # 获得文本对象的字符串而非对象本身 charset = guess_charset(msg) # 要检测文本编码 if charset: content = content.decode(charset) content = '%s' % (content) print(content) # 获取邮件文本内容,如果只有文本,打印显示的结果和邮件中看的效果一模一样 else: print(content_type+'不是文本') print_info(msg, 0) # 退出 conn.quit()
如果程序要获取邮件的发件人、收件人和主题,直接通过 EmailMessage 的相应属性来获取即可,与前面为 EmailMessage 设置发件人、收件人和主题的方式是对应的。
如果程序要读取 EmailMessage 的各部分,则需要调用该对象的 walk() 方法,该方法返回一个可迭代对象,程序使用 for 循环遍历 walk() 方法的返回值,对邮件内容进行逐项处理:
如果邮件某项的 maintype 是 'multipart',则说明这一项是容器,用于包含邮件内容、附件等其他项。
如果邮件某项的 maintype 是 'text',则说明这一项的内容是文本,通常就是邮件正文或文本附件。对于这种文本内容,程序直接将其输出到控制台中。
如果邮件中某个项的 maintype 属性是“其他”,那么这一项的内容就是作为附件的,程序会将附件内容保存到本地文件中。
import os import poplib import mimetypes from email.parser import Parser, BytesParser from email.policy import default msg_data = b'\r\n'.join(lines) # 将字符串内容解析成邮件,此处一定要指定policy=default msg = BytesParser(policy=default).parsebytes(msg_data) print(type(msg)) print('发件人:' + msg['from']) print('收件人:' + msg['to']) print('主题:' + msg['subject']) print('第一个收件人名字:' + msg['to'].addresses[0].username) print('第一个发件人名字:' + msg['from'].addresses[0].username) for part in msg.walk(): counter = 1 # 如果maintype是multipart,说明是容器(用于包含正文、附件等) if part.get_content_maintype() == 'multipart': continue # 如果maintype是multipart,说明是邮件正文部分 elif part.get_content_maintype() == 'text': print(part.get_content()) # 处理附件 else: # 获取附件的文件名 filename = part.get_filename() # 如果没有文件名,程序要负责为附件生成文件名 if not filename: # 根据附件的contnet_type来推测它的后缀名 ext = mimetypes.guess_extension(part.get_content_type()) # 如果推测不出后缀名 if not ext: # 使用.bin作为后缀名 ext = '.bin' # 程序为附件来生成文件名 filename = 'part-%03d%s' % (counter, ext) counter += 1 # 将附件写入的本地文件 with open(os.path.join('.', filename), 'wb') as fp: fp.write(part.get_payload(decode=True)) # 退出服务器,相当于发送POP 3的quit命令 conn.quit()
通过IMAP协议来管理邮箱用的,称作交互邮件访问协议。
! encoding:utf8 ''' 环境: Win10 64位 Python 2.7.5 参考: http://www.pythonclub.org/python-network-application/email-format http://blog.sina.com.cn/s/blog_4deeda2501016eyf.html ''' import imaplib import email def parseHeader(message): """ 解析邮件首部 """ subject = message.get('subject') h = email.Header.Header(subject) dh = email.Header.decode_header(h) subject = unicode(dh[0][0], dh[0][1]).encode('gb2312') # 主题 print subject print ' ' # 发件人 print 'From:', email.utils.parseaddr(message.get('from'))[1] print ' ' # 收件人 print 'To:', email.utils.parseaddr(message.get('to'))[1] print ' ' # 抄送人 print 'Cc:',email.utils.parseaddr(message.get_all('cc'))[1] def parseBody(message): """ 解析邮件/信体 """ # 循环信件中的每一个mime的数据块 for part in message.walk(): # 这里要判断是否是multipart,是的话,里面的数据是一个message 列表 if not part.is_multipart(): charset = part.get_charset() # print 'charset: ', charset contenttype = part.get_content_type() # print 'content-type', contenttype name = part.get_param("name") #如果是附件,这里就会取出附件的文件名 if name: # 有附件 # 下面的三行代码只是为了解码象=?gbk?Q?=CF=E0=C6=AC.rar?=这样的文件名 fh = email.Header.Header(name) fdh = email.Header.decode_header(fh) fname = dh[0][0] print '附件名:', fname # attach_data = par.get_payload(decode=True) # 解码出附件数据,然后存储到文件中 # try: # f = open(fname, 'wb') #注意一定要用wb来打开文件,因为附件一般都是二进制文件 # except: # print '附件名有非法字符,自动换一个' # f = open('aaaa', 'wb') # f.write(attach_data) # f.close() else: #不是附件,是文本内容 print part.get_payload(decode=True) # 解码出文本内容,直接输出来就可以了。 # pass # print '+'*60 # 用来区别各个部分的输出 def getMail(host, username, password, port=993): try: serv = imaplib.IMAP4_SSL(host, port) except Exception, e: serv = imaplib.IMAP4(host, port) serv.login(username, password) serv.select() # 搜索邮件内容 typ, data = serv.search(None, '(FROM "xx@xxx.com")') count = 1 pcount = 1 for num in data[0].split()[::-1]: typ, data = serv.fetch(num, '(RFC822)') text = data[0][1] message = email.message_from_string(text) # 转换为email.message对象 parseHeader(message) print ' ' parseBody(message) pcount += 1 if pcount > count: break serv.close() serv.logout() if __name__ == '__main__': host = "imap.mail_serv.com" # "pop.mail_serv.com" username = "Trevor@mail_serv.com" password = "your_password" getMail(host, username, password)
Atas ialah kandungan terperinci Cara menggunakan modul e-mel, smtplib, poplib, imaplib untuk menghantar dan menerima e-mel dalam Python. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!