zmail库是由国人制作的第三方库,它可以简单的发送和接收邮件。无须手动添加服务器地址、端口以及适合的协议,zmail会帮你完成。
用zmail进行收取邮件尤其是带附件时比较方便,功能能进行强大扩展,比如按照时间、邮件主题进行匹配后获取、保存邮件。本次会给出一个完整的范例,并测试通过。
zmail在收取邮件是默认为邮件全部收取后进行匹配,速度、性能很差,容易假死,在窄带和大量邮件情况下容易翻车。故本范例对读取部分进行了功能和性能优化。
示范代码每次取10封(step=10)邮件,一直读取完所有符合条件(最近N天)的邮件并进行自动保存,避免邮箱过大等待超时,是功能优化后的读取版本,供大家参考。
【完整源码下载见 https://download.csdn.net/download/cdl3/88518767】
【入门】
Zmail仅支持python3,不需要任何外部依赖. 不支持python2.
$ pip3 install zmail
使用它之前,请保证
- 使用Python3
- 确保打开了邮箱的POP3和SMTP功能 (对于 @163.com 和 @gmail.com 你需要设置你的应用专用密码)
然后,剩下你需要做的就是import zmail即可
邮件消息体支持的常用字段
Subject :邮件主题
Content_text :text邮件内容
Content_html :html邮件内容
Attachments :附件
邮件常用方法
send_mail :发送邮件
get_latest:获取最新邮件
get_mail:依据id获取邮件
get_mails:根据条件获取邮件列表
get_headers:获取所有邮件头信息
stat:获取收件箱信息
zmail.show:展示邮件消息
【支持邮箱】
包括常见的126.com/163.com/qq.com/yeah.net/gmail.com/sina.com/outlook,也可以支持阿里、腾讯、网易、谷歌的企业邮箱。当然,也可以自定服务器地址进行额外支持。
本次给出了一个自定义邮件服务器配置的范例供参考。
下面是综合性范例,主要用于扩展下载邮件功能,对日期字段进行匹配,自动收取最近10天(N天)最新邮件,自动保存邮件正文(以文本文件格式和html两种格式保存)和附件,自动跳过已下载的邮件。
【示范下载代码】
import zmail import os,time import datetime cur_path=os.getcwd() def cnt_time(func): import time def inner(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) end_time=time.time() result=end_time-start_time print('func time is %.3fs'%result) return res return inner def is_n_days_before(date1,date2,n): import datetime print(date1,date2) if date1<=date2: return (date2-date1).days<=(n-1) else: return False def gen_another_name(file_path): #判断文件名是否重名,如果已存在则自动另取一个文件名 import os i=0 folder_path, file_name = os.path.split(file_path) filename, ext1=os.path.splitext(file_name) full=file_path f1=filename while True: if os.path.exists(full): i+=1 f1=filename+'('+str(i)+')'+ext1 full=folder_path+'\\'+f1 else: return folder_path+'\\'+f1 #自定义新邮件服务器示范 def get_server(id_str): if id_str=='126': return zmail.server("xxxxx@126.com", "xxxxxxxx") if id_str=='QQ': #密码需要改用 授权码 return zmail.server("xxxxx@qq.com", "xxxxxxx") if id_str=='zoho': #显示邮件地址为mxxxxxx@zohumail.com,但官方文档的用户名必须为xxxxxx@zoho.com return zmail.server("xxxxxxx@zoho.com", "xxxxxxxx", smtp_host='smtp.zoho.com',smtp_port=465,smtp_ssl=True, pop_host='pop.zoho.com',pop_port=995,pop_ssl=True ) return None step=10 #保存邮件正文为文本格式 def save_email_txt(filepath,mail2): if mail2: fujian_names = '' if mail2.get('attachments'): for name, raw in mail2['attachments']: fujian_names += name + ' ' print(f'附件列表为:{fujian_names}') if len(mail2['content_text']) >= 0: contents = mail2['content_text'] # 是个list contents = ' '.join(contents) with open(filepath + '/正文文本.txt', 'w', encoding='utf-8') as f1: f1.write('发件人:' + mail2.get('from', '') + '\n') f1.write('收件人:' + mail2.get('to', '') + '\n') f1.write('时间:' + str(mail2.get('date', '')) + '\n') f1.write('主题:' + mail2.get('subject', '') + '\n') if fujian_names != '': f1.write('附件:' + fujian_names + '\n') f1.write('-' * 30 + '正文' + '-' * 30 + '\n') f1.write(contents) #保存邮件正文为html格式 def save_email_html(filepath,mail2): if mail2: fujian_names = '' if mail2.get('attachments'): for name, raw in mail2['attachments']: fujian_names += name + ' ' #print(f'附件列表为:{fujian_names}') if len(mail2['content_html']) > 0: contents = mail2['content_html'] # 是个list # print(contents) contents = ' '.join(contents) with open(filepath + '/正文html.html', 'w', encoding='utf-8') as f: f.write('<!doctype html>') f.write(f'<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">' + f'<title>{mail2.get("subject", "")}</title>') f.write('</head><body>') f.write(f'<h1>{mail2.get("subject", "")}</h1>') f.write(f'<p><b>From: {mail2.get("from", "")}</b></p>') f.write(f'<p><b>To: {mail2.get("to", "")}</b></p>') f.write(f'<p><b>Date: {mail2.get("date", "")}</b></p>') if fujian_names != '': f.write(f'<p><b>Attachments: {fujian_names}</b></p>') f.write('<p style="border-bottom:1px solid black"><b>Contents:</b></p>') f.write(contents) f.write('</body></html>') def save_successful_txt(filepath,mail2): with open(filepath + '/下载成功.txt', 'w', encoding='utf-8') as f1: s1=f"发件人:{mail2.get('from', '')}"+ '\n' s2=f"收件人:{mail2.get('to', '')}"+ '\n' s3=f"时间:{mail2.get('date', '')}"+ '\n' s4=f"主题:{mail2.get('subject', '')}"+ '\n' f1.write('邮件下载成功!具体信息如下:'+'\n') f1.write(s1+s2+s3+s4) @cnt_time def down_mails_n_days(server_id_str, ndays=3, save_dir=None, step=10): import os server = get_server(server_id_str) if not save_dir: save_dir = os.getcwd() + '\\' + 'download\\' + server_id_str + '邮箱' if not os.path.exists(save_dir): os.makedirs(save_dir) count, box_size = server.stat() print(f'【{server_id_str}邮箱】共有{count}封邮件,邮箱大小为{box_size / 1000000:.2f}MB') i = 0 # mails = server.get_mails(start_time=start_date_str, end_time=end_date_str) while True: mails = server.get_headers(start_index=1+step*i, end_index=step*(i+1)) if mails: print(f'==========本批次有邮件【{len(mails)}】封==========') for mail in mails[::-1]: print(' '*10+'-'*30) print(f"<新邮件>[{mail['id']}]-{mail['subject']}-{mail['date']}") import datetime date_now = datetime.datetime.now() if not is_n_days_before(mail['date'].replace(tzinfo=None),date_now,ndays): print(f'<最大日期为{mail["date"]}>,该日期不在{ndays}天内,本批次【{len(mails)}】封邮件均跳过') break year1 = mail['date'].date().year date_str = (str(mail['date']).split('+')[0]).replace(' ', '-').replace(':', '-') dir_str = safe_str(mail['subject']) year_dir = save_dir + '\\' + str(year1) full_dir_str = year_dir + '\\' + date_str + '-' + dir_str print(f'保存位置:{full_dir_str}') if not os.path.exists(full_dir_str): os.makedirs(full_dir_str) s1=mail.get('subject','') if s1=='': print('Subject为空') if os.path.exists(full_dir_str+'/下载成功.txt'): print('---已下载成功!跳过---') continue try: mail2 = server.get_mail(mail['id']) if s1=='' or mail2.get('content_text','')=='': print(mail2) except: import datetime print(f'get mail-ID【{mail["id"]}】 error!') with open(save_dir+'\\error_log.txt', 'a', encoding='utf-8') as f1: f1.write('-'*50) f1.write(f'{datetime.datetime.now()}') f1.write(f'get mail-ID【{mail["id"]}】 error!') f1.write(f"<邮件信息>[{mail['id']}]-{mail['subject']}-{mail['date']}") continue save_email_txt(full_dir_str,mail2) save_email_html(full_dir_str,mail2) if zmail.save_attachment(mail2, target_path=full_dir_str, overwrite=True): print('-----------------------保存附件成功!') save_successful_txt(full_dir_str,mail2) i += 1 print('*' * i,f'第{i}次循环读取,每次读取{step}封邮件') else: print('==============无邮件了,程序退出-------------------') break def validateTitle(title): #保存文件名中不能有非法字符存在 import re #rstr = r"[\/\\\:\*\?\"\\|]" # '/ \ : * ? " < > |' rstr=r'[\\/:*?"<>|\r\n]+' new_title = re.sub(rstr, "_", title) # 替换为下划线 return new_title def safe_str(o, max_len=220): if o is None: return '' s = str(o).replace('/', ':').replace(':','_').replace('>','').replace('<','').replace(' ','') s=validateTitle(s) if len(s) > max_len: return s[:max_len] return s if __name__ == '__main__': down_mails_n_days('126', ndays=5, step=5)
【参考文档】https://github.com/zhangyunhao116/zmail/blob/master/README-cn.md
【发文章不易,请大家多多点赞、支持!】