python群发邮件

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 = '[email protected]'
# 第三方客户端登录时需要的授权码,不是qq密码
pwd = 'xxxxxxxxxxxxx'
# 发件人的邮箱
sender_qq_mail = '[email protected]'
# 获取当前月份
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 运行截图         

      

python群发邮件_第1张图片

    7.3 邮件内容

    

python群发邮件_第2张图片

 

你可能感兴趣的:(python,smtp)