问题描述:1. A向不认识的B、C、D发送了邮件,B、C、D业务繁忙,对于A的邮件可能不回复,但是A希望得到B、C、D的回复;
2. A的想法是在不删除收发邮件的基础上
第一天[手动],A向B、C、D发邮件,未得到回复
第二天[自动],发件箱的收件人列表[B、C、D],收件箱的发件人列表为[],目标列表[B、C、D],B回复
第三天[自动],发件箱的收件人列表[B、C、D、[B、C、D]],收件箱的发件人列表为[B],目标列表[C、D],C回复
第四天[自动],发件箱的收件人列表[B、C、D、[B、C、D]、[C、D]],收件箱的发件人列表为[B、C],目标列表[D],D不回复
......
第N天[自动],发件箱的收件人列表[B、C、D、[B、C、D]、[C、D]、D、D...D],收件箱的发件人列表为[B、C],目标列表[D],D不回复(可以手工终止)
3. A希望将以上自动部分做成定时任务,并尽可能的减少人工参与
工具环境:Win7(32bit) Python2.7
解决过程:1. 使用python imap收邮件模块检查邮箱中的发件人列表和收件人列表,发现有发件人列表有不在收件人列表的项,都记录下来,作为自动发邮件的目标
连接登陆服务器
mailServer = "imap.gmail.com" #imap收邮件模块的gmail服务器
mailPort = 993 #gmail的imap协议通信端口
mailUser = "[email protected]" #账户
mailPass = "XXXXX" #密码
imapConnect = imaplib.IMAP4_SSL(mailServer, mailPort) #以SSL的形式连接gmail服务
imapConnect.logout()imapConnect.login(mailUser, mailPass) #登陆
imapConnect.logout() #退出
取收件箱的发件人列表
imapConnect.select('INBOX') #进入收件箱
att, receiveItems = imapConnect.search(None, 'ALL')
fromList = [] #收件箱的发件人列表
for item in receiveItems[0].split():
pos, mailData = imapConnect.fetch(item, "(RFC822)")
receiveText = mailData[0][1]
receiveJson = email.message_from_string(receiveText)
pattern = re.compile(r"<(.*?)>", re.I|re.X) #python的正则表达式切割,真的很好用
fromList.extend(pattern.findall(receiveJson['From']))
获取发件箱的收件人列表
imapConnect.select('[Gmail]/&XfJT0ZCuTvY-') #进入发件箱
att, sentItems = imapConnect.search(None, 'ALL')
toList = [] #发件箱的收件人列表
for item in sentItems[0].split():
pos, mailData = imapConnect.fetch(item, "(RFC822)")
sentText = mailData[0][1]
sentJson = email.message_from_string(sentText)
toList.append(sentJson['To'])
对比收件人列表和发件人列表,获取未回复人的列表,作为目标
delList = [] #处理收件箱发件人列表中的子列表
for item in fromList:
itemList = item.split() #将发件人列表中的子列表变成发件人元素,注意是以空字符串切割(包括1个以上的空格字符)
if len(itemList) > 1:
delList.append(item)
fromList.extend(itemList) #将子列表的元素加入发件人列表
for rub in delList:
del fromList[fromList.index(rub)] #删除发件人列表中的子列表
delList = [] #处理发件箱收件人列表中的子列表
for item in toList:
itemList = item.split() #将收件人列表中的子列表变成收件人元素
if len(itemList) > 1:
delList.append(item)
toList.extend(itemList) #将子列表的元素加入收件人列表
for rub in delList:
del toList[toList.index(rub)] #删除收件人列表中的子列表
fromList = set(fromList) #变成集合,去除重复元素
toList = set(toList) #变成集合,去除重复元素
jobList = [] #获取未回复邮件的收件人列表,作为今天的发件对象
for item in toList:
if item not in fromList:
jobList.append(item)
特别注意,imapConnect.select('[Gmail]/&XfJT0ZCuTvY-')、imapConnect.select('INBOX'),这里的发件箱类型、收件箱类型....不同类型的邮箱代码也不一样,可以通过for item in imapConnect.list():print item.split('分隔符')[下标]获得
2. 根据第一步的目标列表,使用python smtp群发邮件
连接登陆退出
smtp = smtplib.SMTP()
server = 'smtp.gmail.com:587' #smtp发邮件模块的gmail服务器
userName = '[email protected]' #账户
passWord = 'XXXXX' #密码
smtp.set_debuglevel(1) #输出调试信息
smtp.connect(server) #连接服务器
smtp.ehlo()
smtp.starttls() #安全模式
smtp.login(userName, passWord) #登录
smtp.quit() #退出
添加信件头
mailFrom = '[email protected]' #发件人
mailTo = argTo #收件人列表
mailMain = MIMEMultipart('related') #设置邮件头
mailMain.set_charset('utf-8')
mailMain['Subject'] = subject
mailMain['From'] = mailFrom
mailMain['To'] = mailTo
mailMain.preamble = 'This is a multi-part message in MIME format.'
添加信件内容
mailAlternative = MIMEMultipart('alternative')
mailMain.attach(mailAlternative)
mailText = MIMEText(htmlText, 'html', 'gb2312') #邮件主信息
mailAlternative.attach(mailText)
添加附件
att = MIMEBase('application', 'octet-stream') #添加附件
att.set_payload(open('C:\\Users\\HK\\Desktop\\杂事\\简历\\Resume.pdf', 'rb').read())
encoders.encode_base64(att)
att.add_header('Content-Disposition', 'attachment; filename="Resume.pdf"')
mailMain.attach(att)
群发邮件
smtp.sendmail(mailFrom, mailTo, content)
time.sleep(30) #为了防止异常断开,添加睡眠时间
特别注意,在邮件群发过程中,发现smtp.sendmail(....)的收件人不能为参数列表,那么就不能群发,然后我根据报错去阅读../Lib/email/header.py发现代码段1如下(其中 s参数是传入的收件人参数)
if chunks and chunks[-1].endswith(' '):
extra = ''
else:
extra = ' '
_max_append(chunks, s, maxlinelen, extra)from email.mime.base import MIMEBase
继续追查 _max_append的来源,阅读../Lib/email/quoprimime.py发现代码段2
def _max_append(L, s, maxlen, extra=''):
if not L:
L. append(s.lstrip())
elif len(L[-1]) + len(s) <= maxlen:
L[-1] += extra + s
else:
L. append(s.lstrip())
原来最终的地方,列表使用append()方法添加元素,当然不能传入列表参数
为了减小影响,又实现我的功能,我选择修改代码段1(header.py的源代码)替换为如下一段
if isinstance(s,basestring):#单个字符串参数
if chunks and chunks[-1].endswith(' '):
extra = ''
else:
extra = ' '
_max_append(chunks, s, maxlinelen, extra)
if isinstance(s,list):#字符串列表参数
for item in s:
if chunks and chunks[-1].endswith(' '):
extra = ''
else:
extra = ' '
_max_append(chunks, item, maxlinelen, extra)L[-1] += extra + s
修改后,重新编译,群发邮件OK
3. 使用win7的dos命令at做定时任务,让它定时执行(只有管理员权限才能使用at命令),如下
C:\windows\system32>at 14:10 /every:Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday "python C:\Users\HK\Desktop\Python\operateMail.py"
或者
C:\windows\system32>at 14:10 /every:Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday "C:\Users\HK\Desktop\Python\operateMail.py"
这里的定时任务步骤,也可以使用python的OS模块写在脚本里,甚至还可以在脚本里控制at定时任务的执行。
具体源代码见 附件。