前言
上一篇文章整理了发送多附件邮件的写法,但是很多时候也会用到接收邮件并根据需求下载对应附件
自动生成文件名
很多时候下载附件的时候,不知道所保存的目录下是否有存在同名的文件,如果有,可能会直接覆盖掉,得不偿失,所有增加这个模块,用于避免同名文件被覆盖
代码如下:
# 自动生成文件名
def auto_file_name(file_name, local_path):
try:
# 分割文件名,反回其文件名和扩展名组成的元组
name_suffix = os.path.splitext(file_name)
num = 1
while True:
# 重新拼接文件名
filename = f"{local_path}/{name_suffix[0]}({num}){name_suffix[-1]}" # file(1).txt
# 判断本地是否存在该文件
isFile = os.path.isfile(filename)
if isFile:
num += 1
else:
break
return filename
except Exception as e:
raise Exception(f"自动生成文件名发生异常抛出,原因:{e}")
if __name__ == '__main__':
fileName = "文件名称.txt"
savePath = "./保存文件的目录"
filePath = auto_file_name(fileName , savePath )
print(filePath)
完整的获取邮件代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import email
import imaplib
from email.utils import parseaddr
from email.header import decode_header
# ######################################################
# 接收邮件解码等模块
# ######################################################
# 自动生成文件名
def auto_file_name(file_name, local_path):
try:
# 分割文件名,反回其文件名和扩展名组成的元组
name_suffix = os.path.splitext(file_name)
num = 1
while True:
# 重新拼接文件名
filename = f"{local_path}/{name_suffix[0]}({num}){name_suffix[-1]}" # file(1).txt
# 判断本地是否存在该文件
isFile = os.path.isfile(filename)
if isFile:
num += 1
else:
break
return filename
except Exception as e:
raise Exception(f"自动生成文件名发生异常抛出,原因:{e}")
# 缩进显示:
def parse_email(msg, indent, mail_content=""):
if indent == 0:
# 邮件的From, To, Subject存在于根对象上:
for header in ['From', 'To', 'Subject']:
value = msg.get(header, '')
if value:
if header == 'Subject':
# 需要解码Subject字符串:
value = decode_str(value)
else:
# 需要解码Email地址:
hdr, addr = parseaddr(value)
name = decode_str(hdr)
value = u'%s <%s>' % (name, addr)
mail_content += '%s%s: %s' % (' ' * indent, header, value) + ' '
if msg.is_multipart():
# 如果邮件对象是一个MIMEMultipart,
# get_payload()返回list,包含所有的子对象:
parts = msg.get_payload()
for n, part in enumerate(parts):
# 递归打印每一个子对象:
return parse_email(part, indent + 1, mail_content)
else:
# 邮件对象不是一个MIMEMultipart,
# 就根据content_type判断:
content_type = msg.get_content_type()
if content_type == 'text/plain' or content_type == 'text/html':
# 纯文本或HTML内容:
content = msg.get_payload(decode=True)
# 要检测文本编码:
charset = guess_charset(msg)
if charset:
content = content.decode(charset)
mail_content += '%sText: %s' % (' ' * indent, content)
else:
# 不是文本,作为附件处理:
mail_content += '%sAttachment: %s' % (' ' * indent, content_type)
return mail_content
# 字符编码转换
def decode_str(str_in):
value, charset = decode_header(str_in)[0]
if charset:
value = value.decode(charset)
return value
# 获得msg的编码
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
# 解析邮件,获取附件
def get_att(msg, savePath):
for part in msg.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
fileName = part.get_filename()
# 如果文件名为纯数字、字母时不需要解码,否则需要解码
try:
fileName = decode_header(fileName)[0][0].decode(decode_header(fileName)[0][1])
except:
pass
# 如果获取到了文件,则将文件保存在指定的目录下
if fileName:
if not os.path.exists(savePath):
os.makedirs(savePath)
filePath = os.path.join(savePath, fileName)
if os.path.isfile(filePath):
filePath = auto_file_name(fileName, savePath)
fp = open(filePath, 'wb')
fp.write(part.get_payload(decode=True))
fp.close()
# ######################################################
# 接收邮件模块
# ######################################################
# 接收邮件
def get_email(json_dict):
try:
emailType = json_dict["emailType"] # 邮箱类型
emailAddress = json_dict["emailAddress"] # 邮箱账号
emailPassword = json_dict["emailPassword"] # 用户密码,授权码
emailCount = json_dict["emailCount"] # 邮件数量
isReadEmail = json_dict["isReadEmail"] # 是否仅未读邮件
isSaveAttachment = json_dict["isSaveAttachment"] # 是否保存附件
savePath = json_dict["savePath"] # 保存目录
smtpData = {
"0": "imap.qq.com", # qq邮箱
"1": "imap.126.com", # 126 邮箱
"2": "imap.163.com" # 163 邮箱
}
if emailType in ["0", "1", "2"]:
mailHost = smtpData[emailType]
else:
raise Exception("暂时不支持该邮箱服务器,请重新选择新的邮箱服务器")
if emailType == "2" or emailType == "1":
imaplib.Commands['ID'] = 'AUTH'
server = imaplib.IMAP4_SSL(mailHost)
server.login(emailAddress, emailPassword)
# 此处用于规避163和126获取邮件时会报错
args = ("name", emailAddress, "contact", emailAddress, "version", "1.0.0", "vendor", "myclient")
typ, dat = server._simple_command('ID', '("' + '" "'.join(args) + '")')
else:
# 连接pop服务器。如果没有使用SSL,将IMAP4_SSL()改成IMAP4()即可其他都不需要做改动
server = imaplib.IMAP4(mailHost)
# 登录--发送者账号和口令
server.login(emailAddress, emailPassword)
# 邮箱中的文件夹,默认为'INBOX'
inbox = server.select("INBOX")
# 是否仅未读邮件
if isReadEmail:
# 搜索匹配的邮件,第一个参数是字符集,None默认就是ASCII编码,第二个参数是查询条件,这里的ALL就是查找全部 UnSeen:未读邮件
type1, emailData = server.search(None, "UnSeen")
else:
type1, emailData = server.search(None, "All")
# 邮件列表,使用空格分割得到邮件索引
msgList = emailData[0].split()
# 邮件数量是否超出所要显示的数量
if emailCount is None or emailCount == "":
emailCount = 1
else:
if int(emailCount) >= len(msgList):
emailCount = len(msgList)
else:
emailCount = int(emailCount)
mailMessageList = []
msgList.reverse()
for i in range(emailCount):
latest = msgList[i]
# 最新邮件,第0封邮件为最早的一封邮件
type1, datas = server.fetch(latest, '(RFC822)')
# 使用utf-8解码
text = datas[0][1].decode('utf8')
# 转化为email.message对象
message = email.message_from_string(text)
content = parse_email(message, 0)
mailMessageList.append(content)
# 是否保存附件
if isSaveAttachment:
if savePath is None or savePath == "":
raise Exception ("附件保存目录为空,请检查附件保存目录是否输入正确")
emailBody = datas[0][1]
mail = email.message_from_bytes(emailBody)
# 获取附件
get_att(mail, savePath)
# 关闭连接
server.close()
return mailMessageList
except Exception as e:
raise Exception (f'接收邮件异常抛出,原因: {e}')
if __name__ == '__main__':
# 接收邮件
data = {
"emailType": "2",
"emailAddress": "xxx@163.com", # 邮箱账号
"emailPassword": "xxxx", # 授权码
"emailCount": '2', # 邮件数量
"isReadEmail": False, # 是否仅未读邮件
"isSaveAttachment": True, # 是否保存附件
"savePath": r"./附件" # 保存目录
}
mailList = get_email(data)
print(mailList )