1. 前言
1.1 应朋友要求,写一个群发邮件的脚本,用来实现往每个人的邮箱里边发送自己的工资条
2. 数据格式,最后一列是邮箱地址
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())
- # ssl登录
- smtp = SMTP_SSL(host_server)
- # set_debuglevel()是用来调试的。参数值为1表示开启调试模式,参数值为0关闭调试模式,
- smtp.set_debuglevel(0)
- smtp.ehlo(host_server)
- smtp.login(sender_qq, pwd)
-
-
- 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:
- """
- 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 运行截图
7.3 邮件内容