邮件系列(二)python zmail接收带附件的邮件(功能优化版)

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

【发文章不易,请大家多多点赞、支持!】

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值