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

之前写过用标准库使用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,谢谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容