1 设计要点
小猪在公司做出纳,干的活却包括了出纳、会计、结算专员等工作,周末都要被无奈在家加班,主要还没有加班费,简直是被公司严重压榨。每个月初都要给每个工长发预付款账单邮件,月中发结算款账单。重复性机械工作。
一个及格线上的程序员,最起码的觉悟就是将重复性的机械工作自动化,于是,在我花了一个多小时,帮她给一部分工长发了一次邮箱后,默默的回来写了这个脚本。
所以,设计要点就是一个字——懒。
恩,就酱。
1.1 需求条件
经过我观察,邮件内容分为两种,这里先说第一种,“结算款”:
(1)邮件内容(content)不变,为固定的txt文本
(2)附件(attch)为每个工长的结算账单(excel文件.xlsx),此文件命名为总账单中自动分割出来的名字(暂时不懂怎么分割出来的=.=),格式为:
#月结算款(#月##号支付)_**_工长名字.xlsx
例如 “ ”
(3)邮件主题(Subject)为附件名(不带后缀名)
(4)邮件接收对象(工长)的名单及其邮箱地址基本不变,偶尔变动
(5)
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
- 增加试用期限制
以上两版更新,请进传送门《小周的Github》