(一)向多人发送邮件(带附件)
一、使用的库
这个程序涉及两个库:smtplib 和 email
这两个库都是Python自带的,所以不需要额外的下载安装。

二、思路和步骤

总体思路很简单,就像我们平常上网是通过HTTP协议一样,我们发送邮件是通过SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)来传输的,而现在我们需要做的就是:

(1)开启邮箱 SMTP 服务:

以 QQ 邮箱为例,开启 SMTP 的路径是:邮箱首页 → 设置 → 账户 → POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 → 开启,如下图:

python邮件发送超大附件 python 批量发邮件 附件_开发语言

(2)设置好SMTP服务器地址;
例如 QQ 邮箱的 SMTP 服务器的地址就是:smtp.qq.com,端口号是 465 或 587

server = smtplib.SMTP("smtp.qq.com",587)

(3)告诉服务器我们的邮箱地址和密码(如果是 QQ 邮箱,那么则是授权码);

server.login(fromaddr , "fuheabperjoybjjg")

(4)设置要发送的邮件内容,例如发送者地址,接收者地址,邮件主题,邮件正文,附件等;

(5)将设置好的邮件内容传给服务器,并发送。

python邮件发送超大附件 python 批量发邮件 附件_python_02

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

fromaddr = "2******9@qq.com"
toaddr = "2******9@qq.com"
msg = MIMEMultipart()
msg['From'] = fromaddr
msg['To'] = toaddr
# 邮件主题
msg['Subject'] = "Hooah"
# 邮件正文
body = "HAHAHA!"

msg.attach(MIMEText(body, 'plain'))

server = smtplib.SMTP("smtp.qq.com",587)	#设置SMTP服务器地址
server.starttls()
server.login(fromaddr , "fuheabperjoybjjg")	#告诉服务器发送者的邮箱地址和密码(如果是QQ邮箱,那么则是授权码)
text = msg.as_string()
server.sendmail(fromaddr, toaddr, text)
server.quit()

三、向多人发送

python邮件发送超大附件 python 批量发邮件 附件_服务器_03

四、发送有附件的邮件

python邮件发送超大附件 python 批量发邮件 附件_服务器_04

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders

fromaddr = "2*******9@qq.com"
toaddr = "2*******9@qq.com"
msg = MIMEMultipart()
msg['From'] = fromaddr
msg['To'] = toaddr
# 邮件主题
msg['Subject'] = "Hooah"
# 邮件正文
body = "HAHAHA!"

msg.attach(MIMEText(body, 'plain'))

# 附件
filename = "蟊.pdf"
attachment = open(filename, 'rb')
part = MIMEBase('application', 'octet-stream')
# 这也可以: part = MIMEBase('application', 'pdf')
part.set_payload((attachment).read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment', filename=filename)

msg.attach(part)

# 设置 SMTP
server = smtplib.SMTP("smtp.qq.com")
server.starttls()
server.login(fromaddr , "fuheabperjoybjjg")
text = msg.as_string()
server.sendmail(fromaddr, toaddr, text)
server.quit()

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_05


(二)遍历邮件获取内容

在正式介绍 imbox 库之前,再分享一个技巧。从上面的代码中也可以看到,当输入密码(授权码)的时候是在代码中明文显示,这就带来了风险。

解决办法:

利用 keyring 库,通过系统密钥环将密码(授权码),预先在本地存储好,后面在代码中调用 keyring 库的方法,通过账号把密码取出来作为变量就可以。

这样即使别人拿到了全部代码,但他的本地中没有预先配置好密码,就不会有泄露信息的风险了。具体使用方法为,首先打开命令行安装:pip install keyring

然后运行py文件:

python邮件发送超大附件 python 批量发邮件 附件_开发语言_06

import keyring

keyring.set_password('api', 'user1', 'keyou520')  # 应用名(指定在哪一个应用中来使用)、用户名、密码

这样就完成了该应用名下该用户名和密码的本地注册,下次要使用密码时,直接调用就可以了。

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_07


一、安装imbox:

pip install imbox二、按照上述keyring的本地注册,完成qq邮箱的授权码注册。

python邮件发送超大附件 python 批量发邮件 附件_服务器_08


三、遍历邮件 (imbox方法)

设置:进入邮箱,选择设置,选择启用IMAP,并将收取选项设置为全部,保存设置,如下图:

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_09


代码:

python邮件发送超大附件 python 批量发邮件 附件_开发语言_10

import keyring
from imbox import Imbox


password=keyring.get_password("qqmail", "261082579@qq.com")

with Imbox('imap.qq.com', '261082579@qq.com', password, ssl=True) as imbox: #服务器、用户名、密码、SSL加密
    # 获取未读邮件
    all_inbox_messages = imbox.messages(unread=True) 	#参数是空的话,就是获取全部邮件
    for uid, message in all_inbox_messages: 	#uid参数是每封邮件的编号,可以用于邮件的标记和删除
        print(message.subject)  # 邮件主题
#        print(message.body['plain']) # 邮件文本格式正文

除了邮件主题及文本格式正文外,我们还有以下常用内容:

python邮件发送超大附件 python 批量发邮件 附件_服务器_11


此外,我们常常会有选择性获取已读、未读和红旗标记邮件:

unread_inbox_messages = imbox.messages(unread=True) # 未读邮件
read_inbox_messages = imbox.messages(unread=False) # 已读邮件
flagged_inbox_messages = imbox.messages(flagged=True) # 红旗标记邮件

也可以通过邮件的收件时间获取邮件:

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_12

inbox_message_before = imbox.messages(date__lt=datetime.date(2021, 1, 18)) 
inbox_message_after = imbox.messages(date__gt=datetime.date(2021, 1, 18)) 
inbox_message_on_date = imbox.messages(date__on=datetime.date(2021, 1, 18))

那么如何筛选指定发件人发送的邮件呢?答案就在于对 message.sent_from 的理解了。message.sent_from 是一个字典元组,在遍历的过程中只需要简单通过 message.sent_from[0]['email'] 就能够将发件人邮箱提取出来,接着做判断就能够达到目的:

python邮件发送超大附件 python 批量发邮件 附件_服务器_13


最后,基于编号的两个重要方法:

(1)标记已读 imbox.mark_seen(uid)

(2)删除邮件 imbox.delete(uid)

以删除邮件为例:

for uid, message in all_inbox_messages: 
    if 满足某种条件的邮件: 
        imbox.delete(uid)

四、imaplib方法

(1)获取邮件中所有的文件夹:

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_14


(2)选择收件箱后,筛选邮件:

python邮件发送超大附件 python 批量发邮件 附件_开发语言_15


收件箱中所有邮件,如果想要筛选出发件人为user2邮箱地址的邮件,对应语句为status, data = server.search(None, ‘(FROM “user2”)’),如下图:

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_16


(3)提取邮件:server.fetch(num, ‘(RFC822)’),返回的是一个元组。

·参数num表示提取的邮件序列号,支持同时提取多个连续邮件消息,例如同时提取邮件序列号为2-5的邮件命令为M.fetch(‘2:5’, ‘(RFC822)’)

·参数’(RFC822)'表示数据项名称,RFC822是电子邮件的标准格式,这里’(RFC822)’等同于BODY[]

python邮件发送超大附件 python 批量发邮件 附件_开发语言_17

如果只想获得邮件头部的内容,可以使用以下代码:server.fetch(num, ‘BODY[HEADER]’)

python邮件发送超大附件 python 批量发邮件 附件_开发语言_18


如果只想获得邮件体的内容,可以使用以下代码:server.fetch(num, ‘BODY[TEXT]’)

python邮件发送超大附件 python 批量发邮件 附件_python_19


(4)email.parser.BytesParser(),将字节对象返回一个消息对象结构,与email.message_from_bytes()效果等价。

字节对象:

python邮件发送超大附件 python 批量发邮件 附件_python_20


消息对象结构:

python邮件发送超大附件 python 批量发邮件 附件_python_21


消息对象结构全览:

python邮件发送超大附件 python 批量发邮件 附件_元组_22

对邮件头的操作:

(5)消息对象结构中,如何获取某个字段:get()

(以邮件头中的subject为例)

python邮件发送超大附件 python 批量发邮件 附件_python_23


(6)解码后以显示中文:(以邮件头中的subject为例)

email.header.decode_header()函数,输入包含编码信息的base64字符串,解析出解码后的字节串和charset,解析器的返回是list套一个tuple,即[(bytes, charset)]

如果charset为None, 说明bytes里面不是字节而是str。

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_24


(7)对邮件头中的发件人的操作为例:

python邮件发送超大附件 python 批量发邮件 附件_python_25


email.utils.parseaddr 专门用来解析邮件地址的,原因是邮件地址很多时候在原文里是这样写的:user1 xxxxxxxx@163.com, 返回一个列表,[user1, xxxxxxxx@163.com]

import imaplib 
import email 
from email.parser import BytesParser	#从邮件解析导入BytesParser类
from email.utils import parseaddr #专门用来解析邮件地址的,原因是邮件地址很多时候在原文里是这样写的:user1 xxxxxxxx@163.com, 返回一个列表,[user1, xxxxxxxx@163.com]
import keyring


server= imaplib.IMAP4('imap.qq.com') 
server.login("261082579@qq.com",keyring.get_password('qq','261082579@qq.com'))
#print(server.list())	#查看所有的文件夹,收件箱一般默认为'INBOX'
server.select('INBOX')	#表示选择收件箱
# 搜索匹配的邮件,第一个参数是字符集,None默认就是ASCII编码,第二个参数是查询条件,ALL就是查找全部,返回的是一个元组
status, data = server.search(None, 'ALL')
#print(data)#返回的是这个收件箱里所有邮件的序列号,按接收时间升序排列,最大的表示最近
typ, content = server.fetch('31','BODY[]')
msg = BytesParser().parsebytes(content[0][1])	#将字节对象返回一个消息对象结构,与email.message_from_bytes效果等价,即email.message_from_bytes(content[0][1])

#对邮件头的操作
s = msg.get('Subject')#或者msg['Subject']print(len(s))
print(s)
s_bytes_char=email.header.decode_header(s)
print(s_bytes_char)
sub=str(s_bytes_char[0][0], s_bytes_char[0][1])
print(sub)
print('-'*90)

addr=msg.get('from')
print(addr)
print(parseaddr(addr))
addr_1=email.header.decode_header(parseaddr(addr)[0])
print(addr_1)
print(str(addr_1[0][0],addr_1[0][1]))

对邮件体的操作,邮件体里可能有纯文本的plain和html两部分,也可能有附件。

(8)walk() 函数,循环信件中的每一个mime的数据块。

python邮件发送超大附件 python 批量发邮件 附件_元组_26


(9)如果循环中有附件,那么获取附件名,并解码以显示中文:

python邮件发送超大附件 python 批量发邮件 附件_元组_27

解码过程和上述操作邮件头标题的过程一样,如下:

python邮件发送超大附件 python 批量发邮件 附件_服务器_28


备注:不同的charset,有不同的应对方法。参考:https://cloud.tencent.com/developer/article/1541116(10)下载附件:get_payload(decode=True)函数, 通常decode设为True,即邮件正文根据每个Content-Transfer-Encoding头解码。

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_29


完整代码:

import imaplib 
import email 
from email.parser import BytesParser	#从邮件解析导入BytesParser类
from email.utils import parseaddr #专门用来解析邮件地址的,原因是邮件地址很多时候在原文里是这样写的:user1 xxxxxxxx@163.com, 返回一个列表,[user1, xxxxxxxx@163.com]
import keyring

server= imaplib.IMAP4('imap.qq.com') 
server.login("261082579@qq.com",keyring.get_password('qq','261082579@qq.com'))
#print(server.list())	#查看所有的文件夹,收件箱一般默认为'INBOX'
server.select('INBOX')	#表示选择收件箱
# 搜索匹配的邮件,第一个参数是字符集,None默认就是ASCII编码,第二个参数是查询条件,ALL就是查找全部,返回的是一个元组
status, data = server.search(None, 'ALL')
#print(data)#返回的是这个收件箱里所有邮件的序列号,按接收时间升序排列,最大的表示最近
typ, content = server.fetch('31','BODY[]')
msg = BytesParser().parsebytes(content[0][1])	#将字节对象返回一个消息对象结构,与email.message_from_bytes效果等价,即email.message_from_bytes(content[0][1])

#对邮件头的操作
#(1)操作邮件标题:
# s = msg.get('Subject')#或者msg['Subject']print(len(s))
# print(s)
# s_bytes_char=email.header.decode_header(s)
# print(s_bytes_char)
# sub=str(s_bytes_char[0][0], s_bytes_char[0][1])
# print(sub)
# print('-'*90)

#(2)操作发件人:
# addr=msg.get('from')
# print(addr)
# print(parseaddr(addr))
# addr_1=email.header.decode_header(parseaddr(addr)[0])
# print(addr_1)
# print(str(addr_1[0][0],addr_1[0][1]))

#对邮件体的操作,邮件里可能有纯文本的plain和html两部分,也可能有附件。
p=msg.walk() 
#print(type(p))
for part in msg.walk():  #循环邮件中的每一个mime的数据块
	#print(part)
	#print('-'*25)   #从结果看有5个part
	fileName = part.get_filename()	#如果是附件,这里就会取出附件的文件名
	try:
		subject = email.header.decode_header(fileName)
	except:
		print('Header decode error')
	else:
		sub_bytes = subject[0][0]
		sub_charset = subject[0][1]
		if sub_charset == None:
			subject = sub_bytes
		elif sub_charset=='unknown-8bit' :
			subject = str(sub_bytes, 'utf8')
		else:
			subject = str(sub_bytes, sub_charset)   
		print(subject)
	#保存附件      
		if subject:  
			with open(subject, 'wb') as fEx:
				data = part.get_payload(decode=True) #通常decode会设为True,即邮件正文根据每个Content-Transfer-Encoding头解码。
				fEx.write(data)  
				print("附件%s已保存" % subject)
	
server.close() #用来关闭当前选择的邮箱
server.logout()#退出登录

上述代码是查看单封邮件的,要遍历整个收件箱的代码如下:

import imaplib 
import email 
from email.parser import BytesParser
from email.utils import parseaddr 
import keyring

server= imaplib.IMAP4('imap.qq.com') 
server.login("261082579@qq.com",keyring.get_password('qq','261082579@qq.com'))
server.select('INBOX')
status, data = server.search(None, 'ALL')
email_list = list(reversed(data[0].split()))
def decode_str(s):
    try:
        subject = email.header.decode_header(s)
    except:
        # print('Header decode error')
        return None 
    sub_bytes = subject[0][0] 
    sub_charset = subject[0][1]
    if None == sub_charset:
        subject = sub_bytes
    elif 'unknown-8bit' == sub_charset:
        subject = str(sub_bytes, 'utf8')
    else:
        subject = str(sub_bytes, sub_charset)
    return subject 
def get_email(num, server):
    typ, content = server.fetch(num, '(RFC822)')
    msg = BytesParser().parsebytes(content[0][1])
    sub = msg.get('Subject')
    for part in msg.walk():  
        fileName = part.get_filename()  
        fileName = decode_str(fileName)
        if None != fileName:
            print('+++++++++++++++++++')
            print(fileName)
   
    print(num, decode_str(sub)) 
for num in email_list:
    get_email(num, server)
server.close()
server.logout()

在补一个稍微复杂一点的,有空研究:

import imaplib
import email
from email.header import decode_header
from email.utils import parseaddr
import keyring


def get_mail(email_address, sqm):
	
	server= imaplib.IMAP4('imap.qq.com') 
	server.login(email_address,sqm)
	server.select("INBOX")	
	type, data = server.search(None, "ALL")	
	msgList = data[0].split()
	latest = msgList[len(msgList) - 1]
	type, datas = server.fetch(latest, '(RFC822)')	#
	# 使用utf-8解码
	text = datas[0][1].decode('utf8')
	# 转化为email.message对象
	message = email.message_from_string(text)
	return message
#邮件的Subject或者Email中包含的名字都是经过编码后的字符串,要正常显示就必须decode,定义 一个decode函数
def decode_str(s):
	value, charset = decode_header(s)[0]
	if charset:
		value = value.decode(charset)
	return value
#为了防止非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('; format=flowed; delsp=yes')
	return charset
#接下来通过循环遍历来读取邮件内容
# 使用全局变量来保存邮件内容
mail_content = '\n'
# indent用于缩进显示:
def print_info(msg, indent=0):
	global mail_content
	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)
			mail_content += '%s%s: %s' % ('  ' * indent, header, value) + '\n'
	parts = msg.get_payload()
	for n, part in enumerate(parts):
		content_type = part.get_content_type()
		if content_type == 'text/plain':
			content = part.get_payload(decode=True)
			# charset = guess_charset(msg)
			charset = 'utf-8'
			if charset:
				content = content.decode(charset)
			mail_content += '%sText:\n %s' % (' ' * indent, content)
		else:
			# 这里没有读取非text/plain类型的内容,只是读取了其格式,一般为text/html
			mail_content += '%sAttachment: %s' % ('  ' * indent, content_type)
	return mail_content
#最后,调用上述函数,输出邮件内容
if __name__ == '__main__':
	email_addr = "261082579@qq.com"
	sqm=keyring.get_password('qq','261082579@qq.com')
	test = print_info(get_mail(email_addr, sqm))   	 
	print("mail content is: %s" % test)

补充:
(11)搜索中文标题邮件:(待确认)

typ, data = server.search(None,‘SUBJECT “融资中国”’.encode(‘utf-8’))

(12)reversed函数:即倒序迭代器

python邮件发送超大附件 python 批量发邮件 附件_python邮件发送超大附件_30


(3)MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型,它是一个互联网标准,扩展了电子邮件标准,使其能够支持:

非ASCII字符文本;非文本格式附件(二进制、声音、图像等);由多部分(multiple parts)组成的消息体;包含非ASCII字符的头信息(Header information)。

这是walk()函数循环的场景基础。

for part in msg.walk():

# 如果ture的话内容是没用的

if not part.is_multipart():

print(part.get_payload(decode=True).decode(‘utf-8’))

# 解码出文本内容,直接输出来就可以了。walk()函数能历遍邮件所有部分,所以通常都把它放到for循环里面使用。然后再使用is_multipart()函数来判断内容是否有用,打印出有用内容最后用get_payload(decode=True).decode(‘utf-8’)解码并且打印到控制台。通常这个循环有两次,第一次是单纯的字符串格式的,能在控制台显示出来的,第二次循环打印的是像HTML的格式,能在浏览器里查看,就像平时看到的邮件那样。参考:

(4)解释一下什么是 payload?

payload 字面意思“有效载荷,有效负荷,有效载重”。

payload维基百科payload中的解释:

在计算机科学与电信领域,负载(英语:Payload)是数据传输中所欲传输的实际信息,通常也被称作实际数据或者数据体。信头与元数据,或称为开销数据,仅用于辅助数据传输。

在计算机病毒或电脑蠕虫领域中,负载指的是进行有害操作的部分,例如:数据销毁、发送垃圾邮件等。比如有一位客户需要支付一笔费用委托货车司机运送一车石油,石油本身的重量、车子的重量、司机的重量等等,这些都属于载重(load)。但是对于该客户来说,他关心的只有石油的重量,所以石油的重量是有效载重(payload,也就是付费的重量)。那么payload对于程序员来说就是在程序中 起关键作用的代码。维基百科给出了这个样的例子:payload通俗一点讲,在程序的世界里,payload(有效载荷)就是对于接收者有用的数据

进阶资料:
(1)
(2)?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2.no_search_link

未完待续,
后面有空还需要落实的是:怎么搜索中文标题邮件,以及检索的几种方式
实验代码如下:

import imaplib 
import email 
from email.parser import BytesParser	#从邮件解析导入BytesParser类
from email.utils import parseaddr #专门用来解析邮件地址的,原因是邮件地址很多时候在原文里是这样写的:user1 xxxxxxxx@163.com, 返回一个列表,[user1, xxxxxxxx@163.com]
import keyring

server= imaplib.IMAP4('imap.qq.com') 
server.login("261082579@qq.com",keyring.get_password('qq','261082579@qq.com'))
#print(server.list())	#查看所有的文件夹,收件箱一般默认为'INBOX'
server.select('INBOX')	#表示选择收件箱
status, data = server.search(None, 'all')
print(data[0].split())#print(data)#返回的是这个收件箱里所有邮件的序列号,按接收时间升序排列,最大的表示最近

status1, data1 = server.search(None, 'OR (FROM"citiccard@bill.citiccard.com")','SUBJECT', "测试".encode('utf-8'))#'OR FROM"citiccard@bill.citiccard.com"',
email_list1 = list(reversed(data1[0].split()))
print(email_list1)


status2, data2 = server.search(None, '(FROM"261082579@qq.com")','SUBJECT', "中信银行信用卡电子账单".encode('utf-8'))
email_list2 = list(reversed(data2[0].split()))
print(email_list2)



"""
or 或的关系 不加就是 and 中文主题要encode utf-8 有的邮箱可能编码也不好使
status, message = imap_object.search(None, 'OR FROM "ooxx@fuck.com"', 'SUBJECT "测试"'.encode('utf-8'))
"""
# mail.search(None, '(FROM "anjali sinha" SUBJECT "test")') 

#'UNSEEN FROM "test@testmail.com" SUBJECT "Subject" SINCE 17-may-1814'