python群发邮件

1. 前言
1.1 应朋友要求,写一个群发邮件的脚本,用来实现往每个人的邮箱里边发送自己的工资条

2. 数据格式,*后一列是邮箱地址%title插图%num

3. 脚本实现的功能
3.1 自定义邮件标题

3.2 记录发送成功或失败的个数,防止发送失败

4. 代码实现

# -*- coding: utf-8 -*-
# @Time : 2021/3/26 10:11
# @Author : liyf–95/02/02
# @File : send_email.py
# @Software: PyCharm

import xlrd
import time
import re
from email.mime.text import MIMEText
from smtplib import SMTP_SSL

from loguru import logger

# qq邮箱smtp服务器
host_server = ‘smtp.qq.com’
# sender_qq为发件人的qq号码
sender_qq = ‘123456@qq.com’
# 第三方客户端登录时需要的授权码,不是qq密码
pwd = ‘xxxxxxxxxxxxx’
# 发件人的邮箱
sender_qq_mail = ‘123456@qq.com’
# 获取当前月份
batch = time.strftime(“%Y-%m”, time.localtime())

suffix = time.strftime(“%Y%m”, time.localtime())

def get_success_error_counts():
“””
用来读取日志文件中的数据,并转成列表形式,方便调用该函数处理列表中的数据,用来做去重处理
:return: 列表
“””
success_email_list = []

try:
with open(f’success_log_{suffix}.txt’, ‘r’, encoding=’utf8′) as f:
results = f.readlines()
for res in results:
success_email_list.append(res.strip())
except Exception:
logger.error(f’success_log_{suffix}.txt 文件不存在,初始化列表为0!’)
success_email_list = success_email_list

error_list = []
try:
with open(f’error_log_{suffix}.txt’, ‘r’, encoding=’utf8′) as f:
results = f.readlines()
for res in results:
restr = res.strip()
email = re.findall(re.compile(r”email’: ‘(.*?)’, ‘”, re.S), restr)[0]
error_list.append(email)
except Exception:
error_list = error_list

return success_email_list, error_list

def read_excel(subject):
“””
读取excel数据
:param subject: 自定义的邮件标题
:return:
“””
workbook = xlrd.open_workbook(‘工资条2.xlsx’)

worksheet = workbook.sheet_by_index(0)

nrows = worksheet.nrows
# 定义一个空列表,用来存放每一个员工的数据,包括表头
total_list = []
for i in range(nrows):
data_list = worksheet.row_values(i)
if data_list[0] == ”:
pass
else:
total_list.append(data_list)
logger.info(f’数据读取完毕,共有 {len(total_list) – 1} 位同事’)
logger.info(‘————————————————————‘)
time.sleep(2)
for k, v in enumerate(total_list[1:]):
msg_content = ”
for i, j in enumerate(v):
if total_list[0][i] == ‘邮箱’:
pass
else:
# 有一些列的数据为空,处理数据
val = ‘无’ if str(v[i]).strip() == ” else v[i]
msg = f’# {total_list[0][i]}:{val}\n’
msg_content += msg
name = v[0]
email_addr = v[-1]
success, error = get_success_error_counts()
logger.info(f’正在向第 {k + 1}/{len(total_list) – 1} 位同事 {name} 发送邮件,请稍等…’)
if len(success) == 0:
# 说明日志中没有数据,即还没有发送成功的例子
# 开始发送数据
email_content = f’尊敬的 {name} 同事,您好,您的 {batch} 月份工资单信息如下:\n{msg_content}# 发送时间:{time.strftime(“%Y-%m-%d %H:%M:%S”, time.localtime())}’
send_email_to_member(email_addr, email_content, name, subject)
logger.info(f’已发送至邮箱:{email_addr},接收人:{name}’)
else:
# 已经有发送成功的例子,并已存入success_log.txt文件中
email_list = [] # 用来存放success_log.txt文件中的邮箱地址,用来去重
for data in success:
email_list.append(str(data).split(‘—->’)[-1])
if email_addr in email_list:
# 如果需要发送邮件的邮箱地址在success_log.txt文件中,则说明该邮箱已经发送过,无需重复发送
logger.warning(f'{name} 同事:{email_addr} 已经发送过了,无需重复发送邮件!!’)
pass
else:
# 正常发送邮件
email_content = f’尊敬的 {name} 同事,你好,您的 {batch} 月份工资单信息如下:\n{msg_content}# 发送时间:{time.strftime(“%Y-%m-%d %H:%M:%S”, time.localtime())}’
send_email_to_member(email_addr, email_content, name, subject)
logger.info(f’已发送至邮箱:{email_addr},接收人:{name}’)
logger.info(‘————————————————————‘)
logger.info(f’脚本运行结束!等待5分钟后自动关闭窗口(或者手动点击窗口右上角关闭)!!!’)
logger.info(‘运行结果:’)
logger.info(f'{len(total_list) – 1} 位同事已全部发送完毕’)
success, error = get_success_error_counts()
logger.info(f’Successd:{len(success)},Failed:{len(error)}’)
time.sleep(300)

def send_email_to_member(email_addr, email_content, name, subject):
“””
发送邮件
:param email_addr: 收件人邮箱地址
:param email_content: 需要发送的正文内容
:param name: 收件人姓名
:return:
“””
# ssl登录
smtp = SMTP_SSL(host_server)
# set_debuglevel()是用来调试的。参数值为1表示开启调试模式,参数值为0关闭调试模式,
smtp.set_debuglevel(0)
smtp.ehlo(host_server)
smtp.login(sender_qq, pwd)

msg = MIMEText(email_content, “plain”, ‘utf-8′)
msg[“Subject”] = subject # 邮件标题
msg[“From”] = sender_qq_mail # 发件人
msg[“To”] = email_addr # 收件人邮箱
try:
smtp.sendmail(sender_qq_mail, email_addr, msg.as_string())
smtp.quit()
msg = f'{name} 发送成功,时间: {time.strftime(“%Y-%m-%d %H:%M:%S”, time.localtime())}—->{email_addr}’
with open(f’success_log_{suffix}.txt’, ‘a’, encoding=’utf8′) as f:
f.write(msg)
f.write(‘\n’)
f.close()
time.sleep(0.5)
except Exception as e:
logger.error(f’发送失败—>{name}\n原因:{e}’)
item = {}
item[‘name’] = name
item[’email’] = email_addr
item[‘error_reason’] = e
item[‘date’] = time.strftime(“%Y-%m-%d %H:%M:%S”, time.localtime())
with open(f’error_log_{suffix}.txt’, ‘a’, encoding=’utf8′) as f:
f.write(str(item))
f.write(‘\n’)
f.close()

if __name__ == ‘__main__’:
subject = input(‘请输入自定义邮件标题:’)
logger.info(f’自定义邮件标题为:{subject}’)
read_excel(subject)
5. 逻辑梳理
5.1 该脚本的使用qq邮箱发送,其中获取授权码可以 点击这里参考博客

5.2 注意事项

5.2.1 excel名称必须为 `工资条2.xlsx`

5.2.2 `邮箱` 列必须为*后一列,`姓名` 列必须在*列,因为代码中 `name=v[0], email_addr=v[-1]` 是固定的。可以自己做适当修改

5.2.3 表头名称可以随意改动,列数也可以随意增减,但要保证 `邮箱` 和 `姓名` 列存在

6. success_log_202103.txt 和 error_log_202103.txt 的作用
6.1 success_log_202103.txt

6.1.1 用来记录发送成功的数据

6.1.2 发送邮件之前,会先读取该txt文件,并判断要发送的email地址是否在txt里边,如果存在,则不发送,防止重复发送

6.2 error_log_202103.txt

6.2.1 一般情况下,没有这个文件,但是由于一些不可控因素,比如邮箱地址不存在或者断网等,会导致发送邮件失败

6.2.2 发送失败之后会把当前发送的数据记录下来,就会生成这个文件

6.2.3 如果该文件有数据,则首先检查是否是邮箱不正确导致的,如果不是,重新运行exe文件

7. 测试
7.1 使用 `pyinstaller -F send_email.py` 打包 py 文件为 exe 可执行文件

7.2 运行截图

%title插图%num

7.3 邮件内容

%title插图%num