python批量发送邮件--包括批量不同附件

1 设计要点

小猪在公司做出纳,干的活却包括了出纳、会计、结算专员等工作,周末都要被无奈在家加班,主要还没有加班费,简直是被公司严重压榨。每个月初都要给每个工长发预付款账单邮件,月中发结算款账单。重复性机械工作。
  一个及格线上的程序员,最起码的觉悟就是将重复性的机械工作自动化,于是,在我花了一个多小时,帮她给一部分工长发了一次邮箱后,默默的回来写了这个脚本。
  所以,设计要点就是一个字——
  恩,就酱。


1.1 需求条件

经过我观察,邮件内容分为两种,这里先说第一种,“结算款”:
(1)邮件内容(content)不变,为固定的txt文本
(2)附件(attch)为每个工长的结算账单(excel文件.xlsx),此文件命名为总账单中自动分割出来的名字(暂时不懂怎么分割出来的=.=),格式为:

#月结算款(#月##号支付)_**_工长名字.xlsx

例如 “ ”

(3)邮件主题(Subject)为附件名(不带后缀名)
(4)邮件接收对象(工长)的名单及其邮箱地址基本不变,偶尔变动
(5)

example.png

1.2 设计思路:

(1)将工长及其邮箱地址存为CSV文件的两列,python中将其读取为字典形式,存储以供后续查询邮箱地址。

通讯录CSV文件示例

上述CSV文件转为字典形式 {"name":"value"}后:
{"周":"1**@qq.com","朱":"2**@qq.com","王":"3**@qq.com"}

(2)遍历文件夹中的附件(.xlsx类型文件),对其进行两种操作,一方面将其名字(不带路径和后缀)提取出来,作为邮件主题(Subject),并对Subject进一步划分,得到其中的人名(工长);另一方面,将其传入MIMEbase模块中转为邮件附件对象。

名称变化:
【结算款_2_张三.xlsx】--> 【结算款_1_张三】--> 【张三】

(3)由上述得到的人名(name),在字典形式的通讯录中,查找相应的地址(value),即为收件人名称和地址
(4)利用python中的email模块和smtp模块,登录自己的邮箱账号,再对每个附件,得到的收件人名和地址,添加附件,发送邮件。done

smtp邮件发送简单描述:
MUA --> MTA --> MDA --> MTA --> MUA

1.3 注意事项

在设计过程中有几点需要注意
(1) 有时一个邮件地址对应两个人名,此时应该在CSV文件中分为两行存储,而不是将两个人名存为同一个键;
(2)有账单.xlsx文件,通讯录里却没存储此人记录,程序应该打印提示没有通讯记录的人名,且不能直接退出,要保证员工看到此提示,此第一版程序还有解决此问题;
(3)此程序发送的邮件内容为纯文本,若要求邮件内容有不同格式(如部分加粗,部分红色),还有小部分需要每次更改的地方(如邮件内容包含当前月份),如何解决?(这就是第二种邮件内容,“预算款”);
(4)重名的,暂时还没碰到,程序中也没给出解决方案。

1.4 运行demo

收件方
收件列表
收件方界面
发件方
发件方界面

2 程序详解

2.1 各变量含义

变量名 含义
sender_host = 'smtp.163.com:25' # 默认服务器地址及端口
sender_user = 'laoliu******@163.com' # 发件人邮箱地址
sender_pwd = '******' # 发件人邮箱密码
sender_name = u'***公司' # 发件人姓名
attach_path = r'C:\Users***\attchfile' # 附件所在文件夹
attach_type = ".xlsx" # 附件后缀名,即类型
addrBook = r'C:\Users***\邮箱联系人表单.csv' # 邮箱通讯录
content_path = r"C:***\content.txt" # 邮箱正文内容.txt
mail_content = "***" # 邮箱正文
addrs = {"张三":"39###@qq.com"} # 邮箱通讯录(字典形式)
attach_file= " 结算款2张三.xlsx" # 附件的文件名(有后缀)
att_name = ["结算款1张三","结算款2王二"] # 附件的文件名(无后缀)
subject = "结算款1张三" # 文件名作为邮件主题
person_name = ["张三","王二"] # 附件的人名
recv_addr = addrs["张三"] = "###@qq.com" # (收件人)附件中人名对应的邮件地址

2.2 各函数及返回值含义

# 根据输入的CSV文件,获取通讯录:{人名:相应的邮箱地址}
def getAddrBook(addrBook)                      #返回addrs
# 根据附件名称中获得的人名,查找通讯录,找到对应的邮件地址
def getRecvAddr(addrs,person_name)    #  返回 recv_addr
# 加载邮件内容
def getMailContent(content_path) 
# 添加附件-
def addAttch(attach_file)        # 返回MIMEbase对象 att
# 发送邮件
def mailSend()

2.3 源代码

import os
import sys
import csv
import smtplib
from email.mime.base import MIMEBase 
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr
from email import encoders

# ========================批量发送邮件测试(二)----邮件内容固定,主题和附件变化=================================


# --------------------发送服务器配置---------------
sender_host = 'smtp.163.com:25'  # 默认服务器地址及端口
sender_user = 'laoliu***@163.com'  
sender_pwd = '123456***'
sender_name = u'上海***公司'

attach_path = r'C:\Users\user\Desktop\MailMaster V1.1\attchfile'   # 附件所在文件夹
attach_type = ".xlsx"      # 附件后缀名,即类型
addrBook = r'C:\Users\user\Desktop\MailMaster V1.1\邮箱联系人表单.csv'  # 邮件地址通讯录
content_path = r"C:\Users\user\Desktop\MailMaster V1.1\content.txt"   # 邮箱正文内容.txt

# --------------根据输入的CSV文件,获取通讯录人名和相应的邮箱地址-------

def getAddrBook(addrBook):
    '''
        @作用:根据输入的CSV文件,形成相应的通讯录字典
        @返回:字典类型,name为人名,value为对应的邮件地址
    '''
    with open(addrBook,'r',encoding='gbk') as addrFile: 
        reader = csv.reader(addrFile)
        name = []
        value = []
        for row in reader:
            name.append(row[0])
            value.append(row[1])
    
    addrs = dict(zip(name, value))
    return addrs

# addrs = {name : value}

# -------------------根据附件名称中获得的人名,查找通讯录,找到对应的邮件地址---------------

def getRecvAddr(addrs,person_name):
    if not person_name in addrs:
        print("没有<"+person_name+">的邮箱地址! 请在联系人中添加此人邮箱后重试。")
        anykey = input("请按任意数字键【0-9】退出程序:")
        if anykey != '':
            time.sleep(1)
            sys.exit(0)
    return addrs[person_name]



# --------------------加载邮件内容-------------------------

def getMailContent(content_path):
    mail_content = ''

    if not os.path.exists(content_path):
        print("文件 content.txt 不存在")
        exit(0)

    with open(content_path,'r') as contentFile:
        contentLines = contentFile.readlines()
        if len(contentLines) < 1:
            print("no content in content.txt ")
            exit(0)
        mail_content = "".join(contentLines) # 将其+""转为字符串就好了
    return mail_content 


# --------------------添加附件-----------------------------------

def addAttch(attach_file):
    att = MIMEBase('application','octet-stream')  # 这两个参数不知道啥意思,二进制流文件
    att.set_payload(open(attach_file,'rb').read())
    # 此时的附件名称为****.xlsx,截取文件名
    att.add_header('Content-Disposition', 'attachment', filename=('gbk','', attach_file.split("\\")[-1]))
    encoders.encode_base64(att)
    return att



# ---------------------发送邮件-----------------------
def mailSend():
    smtp = smtplib.SMTP()   # 新建smtp对象
    smtp.connect(sender_host)
    smtp.login(sender_user, sender_pwd)

    for root,dirs,files in os.walk(attach_path):
        for attach_file in files:      # attach_file : ***_2_***.xlsx
            msg = MIMEMultipart('alternative')
            att_name = attach_file.split(".")[0]
            subject = att_name
            msg['Subject'] = subject   # 设置邮件主题
            person_name = subject.split("_")[-1]

            addrs = getAddrBook(addrBook)
            recv_addr = getRecvAddr(addrs,person_name)
            
            msg['From'] = formataddr([sender_name,sender_user]) # 设置发件人名称
            # msg['To'] = person_name # 设置收件人名称
            msg['To'] = formataddr([person_name,recv_addr]) # 设置收件人名称   
            mail_content = getMailContent(content_path) 
            msg.attach(MIMEText(mail_content))  # 正文  MIMEText(content,'plain','utf-8')
            attach_file = root+"\\"+attach_file
            att = addAttch(attach_file)
            msg.attach(att)  # 附件
            smtp.sendmail(sender_user, [recv_addr,], msg.as_string())  # smtp.sendmail(from_addr, to_addrs, msg)
            print("已发送: "+person_name+" <"+recv_addr+">")       
        smtp.quit()



if __name__ == '__main__':
    print("By 小周")
    mailSend()
    anykey = input("请按回车键(Enter)退出程序:")
    if anykey:
        exit(50)

第一版到此,20180830,待更新
第二版更新,20180904

  • 增加GUI界面;
  • 解决每次连接只能发10封邮件限制;
  • 按任意键退出功能;

第三版更新,20180909

  • 增加试用期限制

转战CSDN博客,更多博客见传送门《xiaozhou的博客主页

以上两版更新,请进传送门小周的Github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容