美文网首页首页投稿(暂停使用,暂停投稿)kivyPython
三行代码发送带附件邮件——Python 使用 yagmail 库

三行代码发送带附件邮件——Python 使用 yagmail 库

作者: NoneLand | 来源:发表于2017-01-07 11:14 被阅读4161次

    之前写过用标准库使用Python Smtplib 和email发送邮件,感觉很繁琐,久了不用之后便忘记了。前几天看知乎哪些Python库让你相见恨晚?,看到了yagmail第三方库,学习过程中遇到一些问题,记录在此处。

    简单使用

    首先,yagmail 在Github上Home Page给出了yagmail的简单介绍以及使用。下面给出一个简洁的示例:

    import yagmail
    
    yag = yagmail.SMTP(user='mailnotifier@126.com', password='nicai?', host='smtp.126.com', port='25')
    body = "老师,你好!这是最近工作的文件,请查收。"
    yag.send(to='aochuan103@126.com', subject='工作文件', contents=[body, 'imag.png', 'test.pdf'])
    print("已发送邮件")
    

    上述代码发送了一个带两个附件的邮件。相比使用SMTPemail库而言,使用yagmail发送邮件的确十分方便,只需要实例化SMTP()类,然后调用send()就可以了。由于代码中上述两个函数使用的是显式值传递,所以参数意义已经十分明确,具体的可以移步Home Page或者直接查看源代码即可,此处不再赘述。值得一提的是,send()函数中的contents参数传递的是一个资源列表,它会自动识别文件格式。(注意,此处需要有image.pngtest.pdf两个文件在脚本同级目录中,当然也可以给出文件的绝对路径)

    发送成功截图如下:


    发送成功截图

    SMTP配置

    上述代码如果自己执行的话会出错,因为如果用我的账号登陆的话,密码肯定不对(我有这么傻么?),而如果换用自己的账号的话,由于没有打开SMTP服务,会邮件服务器拒绝服务。下面以126邮箱为例,说明SMTP配置流程及注意事项。

    登录126邮箱之后,进入设置,然后POP3/SMTP/IMAP设置,打开服务。126会要求你设置客户端授权码。设置之后记住该授权码,该授权码才是传入SMTP()函数中的password **,这样做的目的可以网易考虑了安全性吧,因为SMTP服务默认是关闭的。

    关于不同邮箱的hostpost不同,网易的hostsmtp.126.comsmtp.163.com, port都是25。而yagamil是为gmail设置的,所以参数默认值为Google的邮箱服务。

    smtp设置

    即使上面都设置正确了,仍然可能发送不成功,可能会出现下面的错误:

    smtplib.SMTPDataError: (554, b'DT:SPM 126 smtp2,DMmowAB386QvTnBYBq2kBQ--.21800S3 1483755057,please see http://mail.163.com/help/help_spam_16.htm?ip=106.39.120.163&hostid=smtp2&time=1483755057')
    

    不要惊慌,这是网易邮箱的反垃圾邮件政策,所以不要在主题和正文中出现test以及其他可能会被认为是垃圾邮件的敏感词汇。给出网易邮箱SMTP服务错误码解释,出现不同问题之后可以到上述页面根据错误代码查找原因(比如上面的554, DT:SPM)。

    隐藏密码

    在上面的代码中,直接将账号和密码放在脚本中不太安全或者不太雅观。作者提供两种方式隐藏密码,第一种是使用Keyring库管理账号和密码,但是恕我愚钝,并不知道如何正确使用;第二种便是在用户根目录下生成一个.yagmail文件提供账户信息,下面采用第二种方式隐藏密码。

    首先,windows下无法命名.yagmail文件,所以将yagmail库中的yagmail.py(我的在C:\Program Files\Anaconda3\Lib\site-packages\yagmail目录下面)第293行打开文件的'.'去掉,即改为:

    def _find_user_home_path():
            with open(os.path.expanduser("~/yagmail")) as f:
                return f.read().strip()
    

    具体的行数会根据版本的差异而不同,可以运行

    import yagmail
    yagmail.SMTP()
    

    查看错误信息,如下图所示:

    错误信息

    以.开头的文件名在Linux下面是隐藏的,作者主要是考虑了安全性,所以Windows下在改了之后应该将文件属性设为隐藏(其实,这样安全性也不高。因为我设置了显式隐藏文件(╥╯^╰╥)。后续应该考虑对yagmail文件内容加密**)。

    但是这样仍然不会正常运行,主要是作者提到的一个机制即在不输入密码的情况下会提示输入密码,这样安全性自然很高,但是我在Pycharm调试环境中一直是粗暴地终止,从没有出现所应许的情境。

    所以让我们撸起袖管,大干一场吧。将yagmail.py文件中SMTP()_find_user_home_path()方法修改为如下:

        @staticmethod
        def _find_user_home_path():
            filename = "~/.yagmail"
            if platform.system() == 'Windows':
                    filename = "~/yagmail"
            with open(os.path.expanduser(filename)) as f:
                return eval(f.read().strip())
    

    这里主要是使用了platform库去检测使用的操作系统,然后在不同的操作系统下读取的名字不一样(需要在文件开头import platform)。strip()去掉用户可能输入的多余的空白字符,eval()函数用以将读取的字符串转变为用户信息字典。
    然后将类的初始化方法init函数修改为如下:

    def __init__(self, user=None, password=None, host='smtp.gmail.com', port='587',
                     smtp_starttls=True, smtp_set_debuglevel=0, smtp_skip_login=False,
                     encoding="utf-8", ** kwargs):
            self.log = get_logger()
            self.set_logging()
            if smtp_skip_login and user is None:
                user = ''
            elif user is None:
                userprop = self._find_user_home_path()
                user = userprop['user']
                password = userprop['password'] if 'password' in userprop and password == None else password
                host = userprop['host'] if 'host' in userprop and host == 'smtp.gmail.com' else host
                port = userprop['port'] if 'port' in userprop and port == '587' else port
            self.user, self.useralias = self._make_addr_alias_user(user)
            self.is_closed = None
            self.host = host
            self.port = port
            self.starttls = smtp_starttls
            self.smtp_skip_login = smtp_skip_login
            self.debuglevel = smtp_set_debuglevel
            self.encoding = encoding
            self.kwargs = kwargs
            self.login(password)
            self.cache = {}
            self.unsent = []
            self.log.info('Connected to SMTP @ %s:%s as %s', self.host, self.port, self.user)
            self.num_mail_sent = 0
    

    主要是修改方法的前面,在检测到有没有用户名的情况下,才读取yagmail配置文件,然后提取里面的账户信息。后面之所以用if else类三元操作符进行赋值是为了给予用户最大的灵活性,使得在yagmail中可以不提供全部信息,其余信息可在实例化SMTP()时传值。但是这违背了Zen of Python中的

    There should be one-- and preferably only one --obvious way to do it.

    一条。所以还是建议如果只用一个邮箱作为发送方,直接将所有信息放到yagmail中,比较方便简洁。
    最后,在用户目录下(我的是C:\Users\NoneLan)创建yagmail文件,写入以下信息:

    {'user':'mailnotifier@126.com', 'password':'kehuduanshouquanma', 'host':'smtp.126.com','port':'25'}
    

    此处使用的是Python的字典格式,这样写方便做解析,字典的键值千万不能出错,必须一致,否则会导致提取信息出错。

    修改之后,在脚本中没有出现用户名和密码(此处使用Jupyter Qtconole,三行代码!!!):

    修改之后代码(Jupyter QtConsole) 改正之后隐藏了用户名和密码成功截图

    yagmail库的源代码只有几百行代码,粗略扫过一眼之后,发现它是对SMTP以及email模块的一个智能封装。库的抽象程度越高,我们写的代码越少,但是我们偏离本质更远。这是需要在学习过程中注意的。具体需要理解到哪一种程度应该是由目标所驱动。

    感谢原作者的辛勤付出,能让我们只用三行代码就能无痛发送邮件。这个世界,总是需要一些人踏踏实实地做一些事情,这种人值得尊敬。所以,如果你觉得这个库很好,对你有所裨益,请移步到项目Home Page点一下右上角的Star,谢谢。

    相关文章

      网友评论

        本文标题:三行代码发送带附件邮件——Python 使用 yagmail 库

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