2022年 11月 5日

python群发邮件

1. 前言

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

2. 数据格式,最后一列是邮箱地址

3. 脚本实现的功能

    3.1 自定义邮件标题

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

4. 代码实现
    

  1. # -*- coding: utf-8 -*-
  2. # @Time : 2021/3/26 10:11
  3. # @Author : liyf--95/02/02
  4. # @File : send_email.py
  5. # @Software: PyCharm
  6. import xlrd
  7. import time
  8. import re
  9. from email.mime.text import MIMEText
  10. from smtplib import SMTP_SSL
  11. from loguru import logger
  12. # qq邮箱smtp服务器
  13. host_server = 'smtp.qq.com'
  14. # sender_qq为发件人的qq号码
  15. sender_qq = '123456@qq.com'
  16. # 第三方客户端登录时需要的授权码,不是qq密码
  17. pwd = 'xxxxxxxxxxxxx'
  18. # 发件人的邮箱
  19. sender_qq_mail = '123456@qq.com'
  20. # 获取当前月份
  21. batch = time.strftime("%Y-%m", time.localtime())
  22. suffix = time.strftime("%Y%m", time.localtime())
  23. # ssl登录
  24. smtp = SMTP_SSL(host_server)
  25. # set_debuglevel()是用来调试的。参数值为1表示开启调试模式,参数值为0关闭调试模式,
  26. smtp.set_debuglevel(0)
  27. smtp.ehlo(host_server)
  28. smtp.login(sender_qq, pwd)
  29. def get_success_error_counts():
  30. """
  31. 用来读取日志文件中的数据,并转成列表形式,方便调用该函数处理列表中的数据,用来做去重处理
  32. :return: 列表
  33. """
  34. success_email_list = []
  35. try:
  36. with open(f'success_log_{suffix}.txt', 'r', encoding='utf8') as f:
  37. results = f.readlines()
  38. for res in results:
  39. success_email_list.append(res.strip())
  40. except Exception:
  41. logger.error(f'success_log_{suffix}.txt 文件不存在,初始化列表为0!')
  42. success_email_list = success_email_list
  43. error_list = []
  44. try:
  45. with open(f'error_log_{suffix}.txt', 'r', encoding='utf8') as f:
  46. results = f.readlines()
  47. for res in results:
  48. restr = res.strip()
  49. email = re.findall(re.compile(r"email': '(.*?)', '", re.S), restr)[0]
  50. error_list.append(email)
  51. except Exception:
  52. error_list = error_list
  53. return success_email_list, error_list
  54. def read_excel(subject):
  55. """
  56. 读取excel数据
  57. :param subject: 自定义的邮件标题
  58. :return:
  59. """
  60. workbook = xlrd.open_workbook('工资条2.xlsx')
  61. worksheet = workbook.sheet_by_index(0)
  62. nrows = worksheet.nrows
  63. # 定义一个空列表,用来存放每一个员工的数据,包括表头
  64. total_list = []
  65. for i in range(nrows):
  66. data_list = worksheet.row_values(i)
  67. if data_list[0] == '':
  68. pass
  69. else:
  70. total_list.append(data_list)
  71. logger.info(f'数据读取完毕,共有 {len(total_list) - 1} 位同事')
  72. logger.info('------------------------------------------------------------')
  73. time.sleep(2)
  74. for k, v in enumerate(total_list[1:]):
  75. msg_content = ''
  76. for i, j in enumerate(v):
  77. if total_list[0][i] == '邮箱':
  78. pass
  79. else:
  80. # 有一些列的数据为空,处理数据
  81. val = '无' if str(v[i]).strip() == '' else v[i]
  82. msg = f'# {total_list[0][i]}{val}\n'
  83. msg_content += msg
  84. name = v[0]
  85. email_addr = v[-1]
  86. success, error = get_success_error_counts()
  87. logger.info(f'正在向第 {k + 1}/{len(total_list) - 1} 位同事 {name} 发送邮件,请稍等...')
  88. if len(success) == 0:
  89. # 说明日志中没有数据,即还没有发送成功的例子
  90. # 开始发送数据
  91. email_content = f'尊敬的 {name} 同事,您好,您的 {batch} 月份工资单信息如下:\n{msg_content}# 发送时间:{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}'
  92. send_email_to_member(email_addr, email_content, name, subject)
  93. logger.info(f'已发送至邮箱:{email_addr},接收人:{name}')
  94. else:
  95. # 已经有发送成功的例子,并已存入success_log.txt文件中
  96. email_list = [] # 用来存放success_log.txt文件中的邮箱地址,用来去重
  97. for data in success:
  98. email_list.append(str(data).split('---->')[-1])
  99. if email_addr in email_list:
  100. # 如果需要发送邮件的邮箱地址在success_log.txt文件中,则说明该邮箱已经发送过,无需重复发送
  101. logger.warning(f'{name} 同事:{email_addr} 已经发送过了,无需重复发送邮件!!')
  102. pass
  103. else:
  104. # 正常发送邮件
  105. email_content = f'尊敬的 {name} 同事,你好,您的 {batch} 月份工资单信息如下:\n{msg_content}# 发送时间:{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}'
  106. send_email_to_member(email_addr, email_content, name, subject)
  107. logger.info(f'已发送至邮箱:{email_addr},接收人:{name}')
  108. logger.info('------------------------------------------------------------')
  109. logger.info(f'脚本运行结束!等待5分钟后自动关闭窗口(或者手动点击窗口右上角关闭)!!!')
  110. logger.info('运行结果:')
  111. logger.info(f'{len(total_list) - 1} 位同事已全部发送完毕')
  112. success, error = get_success_error_counts()
  113. logger.info(f'Successd:{len(success)},Failed:{len(error)}')
  114. time.sleep(300)
  115. def send_email_to_member(email_addr, email_content, name, subject):
  116. """
  117. 发送邮件
  118. :param email_addr: 收件人邮箱地址
  119. :param email_content: 需要发送的正文内容
  120. :param name: 收件人姓名
  121. :return:
  122. """
  123. msg = MIMEText(email_content, "plain", 'utf-8')
  124. msg["Subject"] = subject # 邮件标题
  125. msg["From"] = sender_qq_mail # 发件人
  126. msg["To"] = email_addr # 收件人邮箱
  127. try:
  128. smtp.sendmail(sender_qq_mail, email_addr, msg.as_string())
  129. smtp.quit()
  130. msg = f'{name} 发送成功,时间: {time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}---->{email_addr}'
  131. with open(f'success_log_{suffix}.txt', 'a', encoding='utf8') as f:
  132. f.write(msg)
  133. f.write('\n')
  134. f.close()
  135. time.sleep(0.5)
  136. except Exception as e:
  137. logger.error(f'发送失败--->{name}\n原因:{e}')
  138. item = {}
  139. item['name'] = name
  140. item['email'] = email_addr
  141. item['error_reason'] = e
  142. item['date'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  143. with open(f'error_log_{suffix}.txt', 'a', encoding='utf8') as f:
  144. f.write(str(item))
  145. f.write('\n')
  146. f.close()
  147. if __name__ == '__main__':
  148. subject = input('请输入自定义邮件标题:')
  149. logger.info(f'自定义邮件标题为:{subject}')
  150. 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 邮件内容