python自动读取excel文件邮箱列表,自动批量发送邮件项目(附使用方法+代码)

项目简介

该项目功能是利用python自动读取excel文件中的邮箱列表,并批量发送邮件到目标邮箱,进行客户挖掘的。该项目是一对一发送,对方不会看到是群发的邮件(意思是对方看到的是你单独发送给收件方的,而不是群发的,即收件方只是他一个人)。本项目可以使用多个邮箱进行发送,当某一个邮箱发送的失败率高于设定的阈值时,就自动切换下一个邮箱进行发送。对于发送失败的邮件,在切换到下一个邮箱之后会首先发送上个邮箱发送失败的邮件,之后,才进行继续发送新的邮件。对于已经发送过的任务会自动在excel工作簿中的另一个表格中显示。每次建立新的发送任务之前,系统会检查已发送过的excel表格中的邮箱,如果已经发送过,则不再重复发送。项目可实现选择带附件或不带附件发送。

使用方法

  1. 使用之前一定要将项目中附件的路径给改成自己的附件的路径
  2. 本项目使用的是邮箱的SMTP服务,使用自己的邮箱进行发送之前,一定要先登录邮箱,打开smtp服务,并获取邮箱的授权码(相当于密码,是用来在第三方客户端登陆的授权码)。如果不会可参考链接如何开启qq邮箱的smtp服务,并获取授权码
  3. 获取到授权码后,在代码中将自己的邮箱及授权码填写一下
  4. 在excel中填写表头和邮箱
  5. 程序模块未安装的进行安装
  6. 开始运行程序。

项目结构

在这里插入图片描述

完整项目代码

#第6版:
#  coding:utf-8
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate
from email.header import Header
import sys
from importlib import reload
import time
import numpy as np
import xlrd
import random
from openpyxl import load_workbook

def main(workbook, file_list, attachment_path, person_mail, have_attachment):

    """
    :param file_list: 从excel读取的公司信息
    :param attachment_path:  附件的绝对路径
    :param person_mail:  个人邮箱账号信息
    :param have_attachment:  true代表需要带附件发送; false代表不带附件发送
    :return:
    """

    #配置参数
    encoding = 'utf-8'
    global failTimes
    global staus_index
    global fail_sended
    result = 1     # 1 代表正常结束程序; -1代表异常结束
    sta_list=[]   #初始化发送状态,每换一次邮箱进行一次初始化  0代表发送成功,1代表发送失败的状态
    jishu=10
    fail_count_rate=0.5


    # 账号登录信息 及 邮件服务器信息
    smtpHost = 'smtp.' + person_mail['username'].split('@')[1]
    #smtpPort = '25'
    sslPort = person_mail['sslPort']
    username = person_mail['username']
    password = person_mail['password']
    fromMail = person_mail['username']

    # 设置默认字符集为UTF8 不然有些时候转码会出问题
    default_encoding = 'utf-8'
    if sys.getdefaultencoding() != default_encoding:
        reload(sys)
        sys.setdefaultencoding(default_encoding)

    # 登录邮件服务器
    smtp = login_mail_server(smtpHost, sslPort, username, password)
    if not smtp: #若登陆失败,返回-1,换另一个邮箱
        result=-1
        return result, staus_index;


    # 发送上次失败的邮件,重发一次。=====发完之后清空失败列表,继续累计新的失败列表=========================================================待写===========
    for j in range(1, len(fail_sended)):
        time.sleep(random.randint(2,6))

        # 构造邮件目的地
        company, area_company, to_email_list = getemail(fail_sended, j)

        # 构造邮件主题,"+"【中国移动致】北斗高精度定位账号及终端服务+价格相对全国最低+中国移动通信集团有限公司+RTK定位技术"
        subject = "XXXXX" +company+",XXXX"

        # 构造邮件正文
        content = get_mail_text(have_attachment,company, area_company)


        mail_content = MIMEText(content.encode(encoding), 'html', encoding)
        # 构造邮件附件
        if have_attachment:
            # 构造邮件附件
            att1 = MIMEApplication(open(attachment_path, 'rb').read())
            att1['Content-Type'] = 'application/octet-stream'
            # 这里的filename可以任意写,写什么,附件就显示什么名字,这里取真实的文件名字
            att1.add_header('Content-Disposition', 'attachment', filename=attachment_path.split("\\")[-1])

        # msg代表邮件对象
        msg = MIMEMultipart()
        # 绑定邮件主题和来源邮箱到邮件对象
        msg['Subject'] = Header(subject, encoding)
        msg['From'] = Header(fromMail)

        # 绑定邮件 目的地 到邮件对象
        msg['To'] = Header(';'.join(to_email_list), encoding)
        # 帮定邮件正文到邮件对象
        msg.attach(mail_content)

        # 绑定邮件附件到邮件对象
        if have_attachment:
            msg.attach(att1)

        try:
            # 发送邮件
            smtp.sendmail(fromMail, to_email_list, msg.as_string())
            print("正在发送上批发送失败的公司->"+str(j) + ":( " + fromMail + ")" + str(company) + "->" + str(len(to_email_list)) + "封信" + "->" + str(to_email_list))

        except Exception as e:

            print("再次发送失败->+"+str(j + 1) + ":( " + fromMail + ")" + str(company) + ':发送失败,异常原因:' + str(e))
        del msg
    print("上次发送失败数据->重发完毕")

    sended_company=get_leiji_sended_company_xls(workbook)


    #清空上次失败的数据,继续累计新的失败数据
    fail_sended=[file_list[0]]
    failTimes=0
    # 接上次停下的位置继续发送邮件
    for i in range(staus_index+1, len(file_list) ): # 从1 开始,0是表头。  i=1就是第一个公司

        time.sleep(random.randint(2,6))

        # 构造邮件目的地
        company, area_company, to_email_list = getemail(file_list, i)

        # 检查先前是否已发送过。若发送过,则跳过,继续下一个公司
        if company in sended_company:
            print(str(staus_index + 1) + ":( " + fromMail + ")" + str(company) + "->(重复发送)先前任务已发送过了" )
            continue


        # 构造邮件主题,
        subject = "XXXXX" +company+",XXXX"

        # 构造邮件正文
        content = get_mail_text(have_attachment,company, area_company)
        mail_content = MIMEText(content.encode(encoding), 'html', encoding)
        # 构造邮件附件
        if have_attachment:
            # 构造邮件附件
            att1 = MIMEApplication(open(attachment_path, 'rb').read())
            att1['Content-Type'] = 'application/octet-stream'
            # 这里的filename可以任意写,写什么,附件就显示什么名字,这里取真实的文件名字
            att1.add_header('Content-Disposition', 'attachment', filename=attachment_path.split("\\")[-1])

        # msg代表邮件对象
        msg = MIMEMultipart()
        # 绑定邮件主题和来源邮箱到邮件对象
        msg['Subject'] = Header(subject, encoding)
        msg['From'] = Header(fromMail)

        #此行代码已无效,因为已经提前把空邮箱公司去掉了
        if len(to_email_list)==0:
            print(company+'无邮箱')
            # 这里需要把 记录参数 进行更新
            staus_index = i
            continue
        # 绑定邮件 目的地 到邮件对象
        msg['To'] = Header(';'.join(to_email_list), encoding)
        # 帮定邮件正文到邮件对象
        msg.attach(mail_content)

        #绑定邮件附件到邮件对象
        if have_attachment:
            msg.attach(att1)

        try:
            # 发送邮件
            smtp.sendmail(fromMail, to_email_list, msg.as_string())

            sta_list.append(0)
            print(str(staus_index+1) + ":( "+fromMail +")"+ str(company) + "->" + str(len(to_email_list)) + "封信" + "->" + str(to_email_list))
        except Exception as e:
            print(str(staus_index+1) + ":( "+fromMail +")"+str(company) + ':发送失败,异常原因:'+str(e))
            sta_list.append(1)

            fail_sended.append(file_list[i])  # 保存发送失败的公司
            failTimes+=1
            # failedSend_list.append(file_list[i])  # 保存发送失败的公司


        staus_index = i

        del msg
        # 保存发送成功的公司,包含个别发送异常的,异常数量=jishu*fail_count_rate
        saveDataTo_leijiSended(workbook, file_list[i])

        if fail_rate(sta_list,jishu)>fail_count_rate:
            result = -1
            return result, staus_index;


    print('发送完毕')
    smtp.close()
    return result, staus_index;

def get_mail_text(have_attachment,company, area_company):

    # 每个段落有3种方式,每个段落自由组合
    p1=[
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
    ]

    p2=[
         f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        ]
    p3=[
         f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
    ]
    p4 = [
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
    ]
    p5=[
         f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
    ]
    p6=[
       f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
    p7=[
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
        f"XXXXXXXXXXXXXXXXXXXX{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx{area_company}XXXXXXXXXXXXXXXXXXXXXXXXXXx",
    ]
    p8=[]
    p9=[]


    if have_attachment:
        content="""
                          html代码编辑的页面正文,内容可跟随变量进行改变,且可以自由组合,
                 """
    else:
        content = """
                    html代码编辑的页面正文,内容可跟随变量进行改变,且可以自由组合,
                 """




    return content

def saveDataTo_leijiSended(workbook, filelist_row):
    ws = workbook['累计已发送']
    ws1 = workbook['本次任务总表']

    # 获取累计已发送的表头
    title_lieji_sended = []
    for title in ws[1]:
        title_lieji_sended.append(title.value)
    # 获取本次发送任务表头
    title_benci_send = []
    for title1 in ws1[1]:
        title_benci_send.append(title1.value)
    # 将本次任务表头和当前发送行数据封装为字典
    dic=dict(zip(title_benci_send,filelist_row))

    #将该行数据根据”累计已发送的表头“的顺序进行顺序调整到新的列表。
    t=[]
    for i in range(len(title_lieji_sended)):
        t.append(dic[title_lieji_sended[i]])
    # 最后将该行已发送数据保存到”累计已发送“表格中
    ws.append(t)

# 获取 累计已发送 表格数据为列表
def get_leiji_sended_company_xls(workbook):
    leiji_sended_file_list=[]
    # 读取客户信息文件excel
    ws = workbook['累计已发送']

    # 从第一行开始读取公司信息
    for row_tuple in ws.values:
        file_list_row=list(row_tuple)
        leiji_sended_file_list.append(file_list_row)

    company_index = leiji_sended_file_list[0].index("企业名称")
    sended_file_array = np.array(leiji_sended_file_list)
    sended_company = sended_file_array[:, company_index]

    return sended_company

def getemail(file_list,i):
    biaotou=file_list[0]
    email_list=[]
    if file_list[i][biaotou.index('邮箱')] == '-' and file_list[i][biaotou.index('更多邮箱')] != '-':
        to_email_list=file_list[i][biaotou.index('更多邮箱')].split(';')
    if file_list[i][biaotou.index('邮箱')] != '-' and file_list[i][biaotou.index('更多邮箱')] == '-':
        to_email_list = file_list[i][biaotou.index('邮箱')].split(';')
    if file_list[i][biaotou.index('邮箱')] != '-' and file_list[i][biaotou.index('更多邮箱')] != '-':
        email_liststr=file_list[i][biaotou.index('邮箱')]+';'+file_list[i][biaotou.index('更多邮箱')]
        to_email_list=email_liststr.split(';')
    if file_list[i][biaotou.index('邮箱')] == '-' and file_list[i][biaotou.index('更多邮箱')] == '-':
        to_email_list=[]

    area_company = str(file_list[i][biaotou.index('企业类型')])
    company = str(file_list[i][biaotou.index('企业名称')])

    return company, area_company, to_email_list


# 一定要主义有效行数,原来有内容,后来删除行内容,openpyxl会把这些行当作有效行(有内容的行)。所以不要只删除内容,直接把行给删除即可。
def load_customer_xls(workbook):
    file_list=[]
    # 读取客户信息文件excel
    ws = workbook['本次任务总表']

    # 从第一行开始读取公司信息
    for row_tuple in ws.values:
        file_list_row=list(row_tuple)
        file_list.append(file_list_row)
    return file_list

def login_mail_server(smtpHost, sslPort, username, password):
    # 连接登录邮箱
    try:

        # #普通方式登录邮箱,使用内网
        # print('创建smtp对象')
        # smtp = smtplib.SMTP()
        # smtp.connect(smtpHost)
        # smtp.login(username, password)
        # print('登录服务器成功')

        # 纯粹的ssl加密方式,通信过程加密,邮件数据安全--登陆邮箱
        smtp = smtplib.SMTP_SSL(smtpHost, sslPort)
        smtp.ehlo()
        smtp.login(username, password)
        print(username+'登录服务器成功')
    except Exception as e:
        print(username+'登录失败: '+str(e))
        return None

    return smtp


# 计算发送失败率
def fail_rate(sta_list,jishu):
    s=0;
    if len(sta_list)<=jishu:
        for i in range(0, len(sta_list)):
            s = s + sta_list[i]
        return s/jishu
    for i in range(0-jishu,0):
        s=s+sta_list[i]
    return s / jishu



if __name__ == '__main__':
    #个人信息
    persons = [
        {'mailtype': 'qq', 'username': 'XXX@qq.com', 'password': 'XXXb', 'sslPort': "465"},
        {'mailtype': '163', 'username': 'XXX@163.com', 'password': 'XXX', 'sslPort': "465"},
 

    ]

    # 附件地址及发送时候的附件名字
    attachment_path='E:\wps共享文件夹\工作效率提高代码\python自动发送邮件项目\无人机行业解决方案.pdf'

    #个人信息追加端口号
    for person in persons:
        if person['mailtype'] == 'qq':
            person['sslPort'] = '465'
        if person['mailtype'] == '163':
            person['sslPort'] = '465'
        if person['mailtype'] == 'nei':
            person['sslPort'] = '465'

    target_info_file = 'youxiang.xlsx'
    wb = load_workbook(target_info_file)


    # 加载客户信息到列表file_list
    file_list = load_customer_xls(wb)
    # 默认不发送附件
    have_attachment= False
    # 指针,记录目前发送到的位置  采用从零计数
    staus_index = 0

    # 储存失败的邮箱
    fail_sended=[file_list[0]]

    try:
        for i in range(len(persons)):  # 循环使用不同的邮箱
            failTimes = 0

            result, staus_index = main(wb, file_list, attachment_path, persons[i], have_attachment)

            # 发送完毕
            if result == 1:
                # sys.exit()
                break
            # 如果发送失败且连续10个公司发送失败,则换另一个邮箱发送
            if result == -1:
                print(
                    '从第 ' + str(staus_index + 1) + ' 个公司继续开始发送===========换另一个邮箱======================')
        if (i + 1) == len(persons):
            print('邮箱已用完')
    except Exception as e:
        # 程序执行异常结束,保存数据后,退出。
       print("程序执行异常"+str(e))




    # 程序正常执行结束,保存数据后,退出。
    print("最后,保存数据到累计已发送")
    wb.save(target_info_file)
    # 将已发送的公司存储到 本次任务已发送

结语

目前该项目任在使用中,并不断的优化更新,后续会将该项目封装成一个软件,增加操作界面,使其更加的通用,更加的适合普通大众使用。欢迎各位对本项目感兴趣的朋友联系我,与我一起优化本项目。

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盘古开天1666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值