美文网首页
Python3实现自动定时发送邮件功能

Python3实现自动定时发送邮件功能

作者: SamBrother | 来源:发表于2018-05-29 22:58 被阅读1136次
    开篇一张图,内容全靠编…
    有时候我们通过爬虫程序爬取了目标信息后需要将这样数据信息打包压缩再自动发送邮件到某个特定邮箱,那么如何实现Python自动发送邮件的功能呢?接下来我们就来简单的介绍下如何利用Python来实现自动发送邮件的功能。
    Python SMTP发送邮件

    SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件HTML邮件以及带附件的邮件。PythonSMTP支持有smtplibemail两个模块,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对应的valueNone或空值时抛出异常信息,方法代码如下:

        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可以重新设置附件的名称。mimetypespython自带的标准库,可以根据文件的后缀名直接得到文件的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:
                manager.send()
                start_time = end_time
                print('regular send email....%s' % time.ctime(start_time))
            else:
                pass
    

    我们使用多线程来进行异步发送操作:

    manager = EmailManager(**mail_cfgs)
    threading.Thread(target=circle_send, args=(manager,)).start()
    

    注意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):
                manager.send()
                print('send a email at 23:00:00 every day....')
            else:
                pass
    

    同样使用多线程来进行异步发送操作:

    manager = EmailManager(**mail_cfgs)
    threading.Thread(target=regular_send, args=(manager,)).start()
    
    • 经测试,上述两种状况收件人邮箱均能正常收取邮件。至此,自动发送邮件的功能就全部讲完了。


      例子中我们的附件压缩包是已经打包好的,但实际大多数情况是我们一边运行爬虫程序抓取信息,一边打包压缩再通过自动发送邮件程序发送邮件到收件人邮箱,所以接下我们就要谈谈Python如何压缩/解压文件夹的问题,这个放到稍后的文章中介绍,敬请关注!!
    • 这几天真是要热死人的节奏啊!你要问我有多热,我只能用下面的图片告诉你!!


      不说了我要去睡觉觉了!!

    相关文章

      网友评论

          本文标题:Python3实现自动定时发送邮件功能

          本文链接:https://www.haomeiwen.com/subject/pnszjftx.html