有时候我们通过爬虫程序爬取了目标信息后需要将这样数据信息打包压缩再自动发送邮件到某个特定邮箱,那么如何实现Python自动发送邮件的功能呢?接下来我们就来简单的介绍下如何利用Python来实现自动发送邮件的功能。
Python SMTP发送邮件
SMTP
是发送邮件的协议,Python
内置对SMTP
的支持,可以发送纯文本邮件
、HTML
邮件以及带附件的邮件。Python
对SMTP
支持有smtplib
和email
两个模块,email
负责构造邮件,smtplib
负责发送邮件。
本着面向对象编程的思想,首先我们封装一个EmailManager
类, 这样以后只要通过这个类构造出一个对象再调用send()
方法就能轻松实现定时自动发送邮件功能,结构大概如下所示:
class EmailManager:
def __init__(self, **kwargs):
...
def __get_cfg(self, key, throw=True):
...
def __init_cfg(self):
...
def login_server(self):
...
def get_main_msg(self):
...
def get_attach_file(self):
...
def _format_addr(self, s):
...
def send(self):
...
初始化相关配置信息
-
__init__(self, **kwargs)
类似于Java
代码中的构造函数,主要就是进行一些初始化的设置工作,其中参数**kwargs
表示的是传入的是一组可变参数,如:
cfgs={"from":'****', "to":"****", ...}
这里我们主要是对smtp server
服务器、可变参数及附件文件的大小进行一些设定工作。对于smtp
服务器如果选择qq
邮箱就设置为smtp.qq.com
,其他邮箱可以自行google
解决,本例使用的即为QQ
邮箱,附件的大小我设置为10M
,具体的设置请参见代码:
def __init__(self, **kwargs):
'''
constructor
:param kwargs:Variable paramete
'''
self.kwargs = kwargs
self.smtp_server = 'smtp.qq.com'
self.MAX_FILE_SIZE = 10 * 1024 * 1024
设置配置参数
接下来我们就需要处理怎么获取外部设置好的配置参数,参数配置采用的是key-value
的键值对模式,这样方便配置和获取,下面是一个配置文件的参考例子:
mail_cfgs = {'msg_from': 'xxxx@qq.com',
'password': 'xxxx',
'msg_to': ['xxxx@qq.com'],
'msg_subject': 'Python Auto Send Email Test',
'msg_content': 'Hi, boy! Just do it, python!',
'attach_file': r'.\font.zip',
'msg_date': time.ctime()
}
其中:
-
msg_from
:邮件发送者的地址 -
password
:邮件发送者邮箱的密码(这里填的是QQ邮箱的授权码,至于何为授权码,请自行百度或google
设置!!) -
msg_to
:邮件接收者的地址,注意这个是个list,因为邮件的接收者可能不止一个 -
msg_subject
:邮件的主题 -
msg_content
:邮件的主要内容 -
attach_file
:邮件的附件名 -
msg_date
:邮件的发送时间戳
获取配置参数
接下来我们就来写个方法通过读取key
获取这些配置参数,当读取的key
对应的value
为None
或空值时抛出异常信息,方法代码如下:
def __get_cfg(self, key, throw=True):
'''
get the configuration file based on the key
:param key:
:param throw:
:return:
'''
cfg = self.kwargs.get(key)
if throw == True and (cfg is None or cfg == ''):
raise Exception("The configuration can't be empty", 'utf-8')
return cfg
有了读取配置参数的方法就可以将之前配置的mail_cfgs
参数读取出来,我们来写个__init_cfg()
方法来初始化读取到的这些参数信息,以便接下来的程序可以调用相关配置参数。代码如下:
def __init_cfg(self):
self.msg_from = self.__get_cfg('msg_from')
self.password = self.__get_cfg('password')
self.msg_to = ';'.join(self.__get_cfg('msg_to'))
self.msg_subject = self.__get_cfg('msg_subject')
self.msg_content = self.__get_cfg('msg_content')
self.msg_date = self.__get_cfg('msg_date')
# attachment
self.attach_file = self.__get_cfg('attach_file', throw=False)
登录SMTP服务器
好,这样我们就可以在接下来的代码中自由的调用之前的配置参数了。有小伙伴可能会对代码中的self
不甚理解,你可以把它看成是Java
代码中的关键字this
。
所有的配置工作都已经完成了,接下来我们要做的就是登陆smtp
服务器,我这里登陆的是QQ
邮箱的服务器。可能很多小伙伴在网上看见其他大牛写得登陆邮箱代码是这句:server = smtplib.SMTP(self.smtp_server, 25)
,然后你屁颠屁颠的运行自己的脚本,结果发现报错,根本就无法运行,你肯定一头雾水!!
其实这是
QQ
邮箱的问题,它采用的是SMTP_SSL
的加密方式,你只需将代码改为server = smtplib.SMTP_SSL(self.smtp_server, 465)
即可,注意相应的端口号变成了465
。参考代码如下:
def login_server(self):
'''
login server
:return:
'''
server = smtplib.SMTP_SSL(self.smtp_server, 465)
server.set_debuglevel(1)
server.login(self.msg_from, self.password)
return server
这里我们返回这个server
是因为后面的代码中需要用到,用set_debuglevel(1)
就可以打印出和SMTP服务器交互的所有信息。
处理邮件内容
登陆成功后接下来我们就需要处理邮件的主要内容了。一封正常的邮件一般包含有收发件者信息,邮件主题,邮件正文内容,有些邮件还附带有附件(本例就附带压缩包附件),具体的设置参见如下代码:
def get_main_msg(self):
'''
suject content
:return:msg
'''
msg = MIMEMultipart()
# message content
msg.attach(MIMEText(self.msg_content, 'plain', 'utf-8'))
msg['From'] = self._format_addr('From <%s>' % self.msg_from)
msg['To'] = self._format_addr('To <%s>' % self.msg_to)
msg['Subject'] = Header(self.msg_subject, 'utf-8')
msg['Date'] = self.msg_date
# attachment content
attach_file = self.get_attach_file()
if attach_file is not None:
msg.attach(attach_file)
return msg
代码中调用了两个方法:_format_addr()
和get_attach_file()
-
_format_addr()
来格式化一个邮件地址。注意不能简单地传入name addr@example.com,因为如果包含中文,需要通过Header对象进行编码。参考代码如下:
def _format_addr(self, s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))
-
get_attach_file()
是用来获取附件的相关信息
带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个MIMEMultipart
对象代表邮件本身,然后往里面加上一个MIMEText
作为邮件正文,再继续往里面加上表示附件的MIMEBase
对象即可,我们把附件那部分抽取到get_attach_file()
方法中。代码如下:
def get_attach_file(self):
'''
generate mail attachment content
:return:
'''
if self.attach_file is not None and self.attach_file != '':
try:
if getsize(self.attach_file) > self.MAX_FILE_SIZE:
raise Exception('The attachment is too large and the upload failed!!')
with open(self.attach_file, 'rb') as file:
ctype, encoding = mimetypes.guess_type(self.attach_file)
if ctype is None or encoding is not None:
ctype = 'application/octet-stream'
maintype, subtype = ctype.split('/', 1)
mime = MIMEBase(maintype, subtype)
mime.set_payload(file.read())
# set header
mime.add_header('Content-Disposition', 'attachment',
filename=os.path.basename(self.attach_file))
mime.add_header('Content-ID', '<0>')
mime.add_header('X-Attachment-Id', '0')
# set the attachment encoding rules
encoders.encode_base64(mime)
return mime
except Exception as e:
print('%s......' % e)
return None
else:
return None
这里我们先对附件的大小进行判断,如果超过我们设置的10M
上限就抛出异常信息,否则就对附件进行一些必要的参数设置,比如信息头和编码规则等。mime.add_header('Content-Disposition','attachment',filename=os.path.basename(self.attach_file))
这里的filename
可以重新设置附件的名称。mimetypes
是python
自带的标准库,可以根据文件的后缀名直接得到文件的MIME
类型。
发送邮件
接下来我们就再写个发送邮件的方法send()
,主要就是将上面所讲的方法串联起来,这样当我们生成一个EmailManager
对象后可以直接通过对象调用send()
方法进行邮件发送操作。send()
方法也很简单,直接上代码:
def send(self):
try:
# initialize the configuration file
self.__init_cfg()
# log on to the SMTP server and verify authorization
server = self.login_server()
# mail content
msg = self.get_main_msg()
# send mail
server.sendmail(self.msg_from, self.msg_to, msg.as_string())
server.quit()
print("Send succeed!!")
except smtplib.SMTPException:
print("Error:Can't send this email!!")
这里需要注意的是msg_to
,即收件人列表,这是个list,之前的mail_cfgs
参数配置信息里特地用的是[]
,这就表示这里可以放多个收件人地址。有人要问那要是我还抄送邮件给别人怎么办?莫急!只需在配置文件中再配置一个'msg_cc':[xxxx@qq.com,****@qq.com]
即可,同时在修改以下方法即可:
def __init_cfg(self):
...
self.msg_cc = self.__get_cfg('msg_cc')
...
def get_main_msg(self):
...
msg['Cc'] = self._format_addr('To <%s>' % self.msg_cc)
...
def send(self):
...
server.sendmail(self.msg_from, self.msg_to + self.msg_cc, msg.as_string())
...
把抄送邮件列表同收件人列表链接起来作为一个参数传进去就OK了。
测试
上面已经将EmailManager这个类的全部方法实现了,接下来我们写个测试程序测试下这个类是否能正常发送邮件。
if __name__ == "__main__":
mail_cfgs = {'msg_from': 'xxxx@qq.com',
'password': 'xxxxx',
'msg_to': ['xxxxx@qq.com'],
'msg_subject': 'Python Auto Send Email Test',
'msg_content': 'Hi, boy! Just do it, python!',
'attach_file': r'.\font.zip',
'msg_date': time.ctime()
}
manager = EmailManager(**mail_cfgs)
manager.send()
测试结果显示收件人的邮箱能正常接收邮件及附件。
但是我们写这个Python程序的初始目的是让它能自己发送邮件,即无人值守发送,看来我们还得改改这个程序。
- 设置每5分钟向收件人邮箱发送一封邮件
#第一种方式发送
def circle_send(manager):
time_intvl = 5 * 60
start_time = int(time.time())
print(start_time)
while True:
end_time = int(time.time())
cost_time = end_time - start_time
# print(cost_time)
if cost_time == time_intvl:
threading.Thread(target=manager.send()).start()
start_time = end_time
print('regular send email....%s' % time.ctime(start_time))
else:
pass
我们使用多线程来进行异步发送操作:
manager = EmailManager(**mail_cfgs)
circle_send(manager)
注意args=(manager,)
中的,
不可以去掉,不然程序报错。程序执行后会每5分钟向收件人邮箱发送一封email,只要程序不停止邮件就会不停的发下去!!
- 每天23:00:00定时发送邮件到收件人邮箱
def regular_send(manager):
regular_hour = 23
regular_min = 00
regular_sec = 00
while True:
current_time = time.localtime(time.time())
# print(current_time.tm_min)
if (current_time.tm_hour == regular_hour) \
and (current_time.tm_min == regular_min) \
and (current_time.tm_sec == regular_sec):
threading.Thread(target=manager.send()).start()
print('send a email at 23:00:00 every day....')
else:
pass
同样使用多线程来进行异步发送操作:
manager = EmailManager(**mail_cfgs)
circle_send(manager)
- 上面都是通过硬编码的方式实现定时发送功能,这样做虽然功能实现了但是
code
太多,实现方式低级!接下来,我们通过使用python任务定时运行库schedule
模块来实现定时功能,简单方便省事,代码量瞬间缩减大半,是不是so easy
?!
#第二种方式,使用python任务定时运行库 schedule 模块
def send_mail_by_schedule(manager):
schedule.every(5).minutes.do(manager.send()) #每5分钟执行一次
schedule.every().hour.do(manager.send()) #每小时执行一次
schedule.every().day.at("23:00").do(manager.send()) #每天23:00执行一次
schedule.every().monday.do(manager.send()) #每周星期一执行一次
schedule.every().wednesday.at("22:15").do(manager.send()) #每周星期三22:15执行一次
while True:
schedule.run_pending()
time.sleep(1)
if __name__=="__main__":
manager = EmailManager(**mail_cfgs)
send_mail_by_schedule(manager)
这种方式也可以实现定时发送邮件功能,代码量也少,但是假如每5分钟运行100个任务,每个任务耗时2分钟,这样在下一个5分钟到来时,上一轮的任务仍在运行,然后又开始了新一轮的任务。解决这个问题的方法是,为每个任务创建一个线程,让任务在线程中运行,从而并行工作。
def run_threaded(manager):
threading.Thread(target=manager.send()).start()
def send_mail_by_schedule(manager):
schedule.every(5).minutes.do(run_threaded, manager) #每5分钟执行一次
while True:
schedule.run_pending()
time.sleep(1)
if __name__=="__main__":
manager = EmailManager(**mail_cfgs)
send_mail_by_schedule(manager)
- 以上两种方式推荐使用
python
的schedule
库实现定时功能,毕竟我一直提倡Code less, Do more!!的做法! -
经测试,上述两种状况收件人邮箱均能正常收取邮件。至此,自动发送邮件的功能就全部讲完了。
例子中我们的附件压缩包是已经打包好的,但实际大多数情况是我们一边运行爬虫程序抓取信息,一边打包压缩再通过自动发送邮件程序发送邮件到收件人邮箱,所以接下我们就要谈谈Python
如何压缩/解压文件夹的问题,这个放到稍后的文章中介绍,敬请关注!!
-
这几天真是要热死人的节奏啊!你要问我有多热,我只能用下面的图片告诉你!!
不说了我要去睡觉觉了!!