python实现email功能

8/7/2023 python
#!/usr/bin/python
# -*- encoding: utf-8 -*-
'''
@Author: apophis
@File: email_uitls.py
@Time: 2021/01/14 20:17:13
@Description: 工程描述
'''
# here put the import lib

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

MAIL_CONF = {
    'sender_account': 'xxxxxx',
    'sender_name': 'xxxxxx',
    'sender_password': 'xxxxxx',
    'smtp_server_host': 'smtp.exmail.qq.com',
    'smtp_server_port': 465
}
TO_ADD = ['apophis@xxx.com']

HTML = """
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<body>
{body}
</body>
</html>
"""
DIV = """
<div id="container">
  <p>{label}</p>
  <div id="content">
   <table border="1" bordercolor="#000000" cellpadding="2" cellspacing="0" style="text-align: center; font-size: 10pt; border-collapse: collapse;" width="50%">
  <tr>
    {head}
  </tr>
  {excel}
</table>
</div>
</div>
"""
row = '<td width="14%%" nowrap=""><font size="2" face="微软雅黑 Light"><div style="text-align: center;">&nbsp;%s</div></font></td>'


def attach_part(email_obj, source_path, part_name):
    '''
    添加附件:附件可以为照片,也可以是文档
    :param email_obj:邮件对象
    :param source_path:附件源文件路径
    :param part_name:附件名
    :return:
    '''
    part = MIMEBase('application',
                    'octet-stream')  # 'octet-stream': binary data   创建附件对象
    part.set_payload(open(source_path, 'rb').read())  # 将附件源文件加载到附件对象
    encoders.encode_base64(part)
    part.add_header('Content-Disposition',
                    'attachment',
                    filename=('gbk', '', '%s' % part_name))  # 给附件添加头文件
    email_obj.attach(part)


def combine_mail(email_obj, email_host, host_port, from_addr, pwd,
                 to_addr_list):
    '''
    发送邮件
    :param email_obj:邮件对象
    :param email_host:SMTP服务器主机
    :param host_port:SMTP服务端口号
    :param from_addr:发件地址
    :param pwd:发件地址的授权码,而非密码
    :param to_addr_list:收件地址
    :return:发送成功,返回 True;发送失败,返回 False
    '''

    try:
        '''
            # import smtplib
            # smtp_obj = smtplib.SMTP([host[, port[, local_hostname]]] )
                # host: SMTP服务器主机。
                # port: SMTP服务端口号,一般情况下SMTP端口号为25。
            # smtp_obj = smtplib.SMTP('smtp.qq.com', 25)
        '''
        smtp_obj = smtplib.SMTP_SSL(email_host, host_port)  # 连接 smtp 邮件服务器
        smtp_obj.login(from_addr, pwd)
        smtp_obj.sendmail(
            from_addr, to_addr_list,
            email_obj.as_string())  # 发送邮件:email_obj.as_string():发送的信息
        smtp_obj.quit()  # 关闭连接
        return True
    except Exception:
        traceback.print_exc()
        return False


def format_html(labels, df_arr):
    '''填充邮件主题,内容,设计排版后返回html字符串'''
    body = ''
    for _ in range(len(df_arr)):
        df = df_arr[_]
        head = ''.join(row % i for i in df.columns.values)
        # 加标签<tr><td></td></tr>
        df = df.applymap(lambda x: '<td>%s</td>' % x)
        df.iloc[:, 0] = df.iloc[:, 0].apply(lambda x: '<tr>%s' % x)
        df.iloc[:, -1] = df.iloc[:, -1].apply(lambda x: '%s</tr>' % x)
        row_list = tuple(''.join(i) for i in df.values)
        excel = ''.join(row_list)
        body += DIV.format(label='' if len(labels) == 0 else labels[_],
                           head=head,
                           excel=excel)
    return body


def get_email_obj(subject, from_list, to_list):
    '''填充邮件主题,发件人,收件人'''
    email_obj = MIMEMultipart()
    email_obj['Subject'] = Header(subject, 'utf-8')
    email_obj['From'] = Header(from_list, 'utf-8')
    email_obj['To'] = Header(','.join(to_list), 'utf-8')
    return email_obj


def attach_content(email_obj,
                   email_content,
                   content_type='html',
                   charset='utf-8'):
    '''
    创建邮件正文,并将其附加到跟容器:邮件正文可以是纯文本,也可以是HTML(为HTML时,需设置content_type值为 'html')
    :param email_obj:邮件对象
    :param email_content:邮件正文内容
    :param content_type:邮件内容格式 'plain'、'html'..,默认为纯文本格式 'plain'
    :param charset:编码格式,默认为 utf-8
    :return:
    '''
    content = MIMEText(email_content, content_type, charset)  # 创建邮件正文对象
    email_obj.attach(content)  # 将邮件正文附加到根容器


def run(email_subject, email_content, to_addr_list):
    ''''''
    email_host = MAIL_CONF['smtp_server_host']
    host_port = MAIL_CONF['smtp_server_port']
    email_from = MAIL_CONF['sender_name']
    from_addr = MAIL_CONF['sender_account']
    pwd = MAIL_CONF['sender_password']

    email_obj = get_email_obj(email_subject, email_from, to_addr_list)
    attach_content(email_obj, email_content)

    return combine_mail(email_obj, email_host, host_port, from_addr, pwd,
                        to_addr_list)


def send_mail(*args, **kwargs):
    subject, df_arr = args[:2]
    try:
        user_address = args[2]
    except Exception:
        user_address = TO_ADD
    labels = kwargs.get('labels', [])
    content = HTML.format(body=format_html(labels, df_arr))
    if run(subject, content, user_address):
        print('send mail success')
    else:
        print('send mail failed')


if __name__ == "__main__":
    import pandas as pd
    import numpy as np
    df = pd.DataFrame(np.random.randn(4, 4),
                      index=list('ABCD'),
                      columns=list('ABCD'))
    send_mail('邮件主题', [df, df], ['apophis@2xx9.com'], labels=['标签1', '标签2'])
    send_mail('邮件主题', [df], ['apophis@2xx9.com'], labels=['标签1'])
    send_mail('邮件主题', [df], ['apophis@2xx9.com'])
    send_mail('邮件主题', [df])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
Last Updated: 6/1/2024, 6:36:28 AM