三行代碼發(fā)送帶附件郵件——Python 使用 yagmail 庫(kù)無(wú)痛發(fā)送郵件

之前寫過(guò)用標(biāo)準(zhǔn)庫(kù)使用Python Smtplib 和email發(fā)送郵件茫陆,感覺(jué)很繁瑣颤芬,久了不用之后便忘記了。前幾天看知乎哪些Python庫(kù)讓你相見(jiàn)恨晚?卓囚,看到了yagmail第三方庫(kù)瘾杭,學(xué)習(xí)過(guò)程中遇到一些問(wèn)題,記錄在此處哪亿。

簡(jiǎn)單使用

首先粥烁,yagmail 在Github上Home Page給出了yagmail的簡(jiǎn)單介紹以及使用。下面給出一個(gè)簡(jiǎn)潔的示例:

import yagmail

yag = yagmail.SMTP(user='mailnotifier@126.com', password='nicai?', host='smtp.126.com', port='25')
body = "老師蝇棉,你好讨阻!這是最近工作的文件,請(qǐng)查收篡殷。"
yag.send(to='aochuan103@126.com', subject='工作文件', contents=[body, 'imag.png', 'test.pdf'])
print("已發(fā)送郵件")

上述代碼發(fā)送了一個(gè)帶兩個(gè)附件的郵件钝吮。相比使用SMTPemail庫(kù)而言,使用yagmail發(fā)送郵件的確十分方便板辽,只需要實(shí)例化SMTP()類奇瘦,然后調(diào)用send()就可以了。由于代碼中上述兩個(gè)函數(shù)使用的是顯式值傳遞戳气,所以參數(shù)意義已經(jīng)十分明確链患,具體的可以移步Home Page或者直接查看源代碼即可巧鸭,此處不再贅述瓶您。值得一提的是,send()函數(shù)中的contents參數(shù)傳遞的是一個(gè)資源列表,它會(huì)自動(dòng)識(shí)別文件格式呀袱。(注意贸毕,此處需要有image.pngtest.pdf兩個(gè)文件在腳本同級(jí)目錄中,當(dāng)然也可以給出文件的絕對(duì)路徑)

發(fā)送成功截圖如下:


發(fā)送成功截圖

SMTP配置

上述代碼如果自己執(zhí)行的話會(huì)出錯(cuò)夜赵,因?yàn)槿绻梦业馁~號(hào)登陸的話明棍,密碼肯定不對(duì)(我有這么傻么?)寇僧,而如果換用自己的賬號(hào)的話摊腋,由于沒(méi)有打開SMTP服務(wù),會(huì)郵件服務(wù)器拒絕服務(wù)嘁傀。下面以126郵箱為例兴蒸,說(shuō)明SMTP配置流程及注意事項(xiàng)。

登錄126郵箱之后细办,進(jìn)入設(shè)置橙凳,然后POP3/SMTP/IMAP設(shè)置,打開服務(wù)笑撞。126會(huì)要求你設(shè)置客戶端授權(quán)碼岛啸。設(shè)置之后記住該授權(quán)碼,該授權(quán)碼才是傳入SMTP()函數(shù)中的password **茴肥,這樣做的目的可以網(wǎng)易考慮了安全性吧坚踩,因?yàn)镾MTP服務(wù)默認(rèn)是關(guān)閉的。

關(guān)于不同郵箱的hostpost不同炉爆,網(wǎng)易的hostsmtp.126.comsmtp.163.com, port都是25堕虹。而yagamil是為gmail設(shè)置的,所以參數(shù)默認(rèn)值為Google的郵箱服務(wù)芬首。

smtp設(shè)置

即使上面都設(shè)置正確了赴捞,仍然可能發(fā)送不成功,可能會(huì)出現(xiàn)下面的錯(cuò)誤:

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')

不要驚慌郁稍,這是網(wǎng)易郵箱的反垃圾郵件政策赦政,所以不要在主題和正文中出現(xiàn)test以及其他可能會(huì)被認(rèn)為是垃圾郵件的敏感詞匯。給出網(wǎng)易郵箱SMTP服務(wù)錯(cuò)誤碼解釋耀怜,出現(xiàn)不同問(wèn)題之后可以到上述頁(yè)面根據(jù)錯(cuò)誤代碼查找原因(比如上面的554恢着, DT:SPM)。

隱藏密碼

在上面的代碼中财破,直接將賬號(hào)和密碼放在腳本中不太安全或者不太雅觀掰派。作者提供兩種方式隱藏密碼,第一種是使用Keyring庫(kù)管理賬號(hào)和密碼左痢,但是恕我愚鈍靡羡,并不知道如何正確使用系洛;第二種便是在用戶根目錄下生成一個(gè).yagmail文件提供賬戶信息,下面采用第二種方式隱藏密碼略步。

首先描扯,windows下無(wú)法命名.yagmail文件,所以將yagmail庫(kù)中的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()

具體的行數(shù)會(huì)根據(jù)版本的差異而不同绽诚,可以運(yùn)行

import yagmail
yagmail.SMTP()

查看錯(cuò)誤信息,如下圖所示:

錯(cuò)誤信息

以.開頭的文件名在Linux下面是隱藏的杭煎,作者主要是考慮了安全性恩够,所以Windows下在改了之后應(yīng)該將文件屬性設(shè)為隱藏(其實(shí),這樣安全性也不高羡铲。因?yàn)槲以O(shè)置了顯式隱藏文件(╥╯^╰╥)玫鸟。后續(xù)應(yīng)該考慮對(duì)yagmail文件內(nèi)容加密**)。

但是這樣仍然不會(huì)正常運(yùn)行犀勒,主要是作者提到的一個(gè)機(jī)制即在不輸入密碼的情況下會(huì)提示輸入密碼屎飘,這樣安全性自然很高,但是我在Pycharm調(diào)試環(huán)境中一直是粗暴地終止贾费,從沒(méi)有出現(xiàn)所應(yīng)許的情境钦购。

所以讓我們擼起袖管,大干一場(chǎng)吧褂萧。將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庫(kù)去檢測(cè)使用的操作系統(tǒng)押桃,然后在不同的操作系統(tǒng)下讀取的名字不一樣(需要在文件開頭import platform)。strip()去掉用戶可能輸入的多余的空白字符导犹,eval()函數(shù)用以將讀取的字符串轉(zhuǎn)變?yōu)橛脩粜畔⒆值洹?br> 然后將類的初始化方法init函數(shù)修改為如下:

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

主要是修改方法的前面唱凯,在檢測(cè)到有沒(méi)有用戶名的情況下,才讀取yagmail配置文件谎痢,然后提取里面的賬戶信息磕昼。后面之所以用if else類三元操作符進(jìn)行賦值是為了給予用戶最大的靈活性,使得在yagmail中可以不提供全部信息节猿,其余信息可在實(shí)例化SMTP()時(shí)傳值票从。但是這違背了Zen of Python中的

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

一條。所以還是建議如果只用一個(gè)郵箱作為發(fā)送方滨嘱,直接將所有信息放到yagmail中峰鄙,比較方便簡(jiǎn)潔。
最后太雨,在用戶目錄下(我的是C:\Users\NoneLan)創(chuàng)建yagmail文件吟榴,寫入以下信息:

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

此處使用的是Python的字典格式,這樣寫方便做解析囊扳,字典的鍵值千萬(wàn)不能出錯(cuò)吩翻,必須一致梅惯,否則會(huì)導(dǎo)致提取信息出錯(cuò)。

修改之后仿野,在腳本中沒(méi)有出現(xiàn)用戶名和密碼(此處使用Jupyter Qtconole,三行代碼K=抛鳌!):

修改之后代碼(Jupyter QtConsole)
改正之后隱藏了用戶名和密碼成功截圖

yagmail庫(kù)的源代碼只有幾百行代碼缔刹,粗略掃過(guò)一眼之后球涛,發(fā)現(xiàn)它是對(duì)SMTP以及email模塊的一個(gè)智能封裝。庫(kù)的抽象程度越高校镐,我們寫的代碼越少亿扁,但是我們偏離本質(zhì)更遠(yuǎn)。這是需要在學(xué)習(xí)過(guò)程中注意的鸟廓。具體需要理解到哪一種程度應(yīng)該是由目標(biāo)所驅(qū)動(dòng)从祝。

感謝原作者的辛勤付出,能讓我們只用三行代碼就能無(wú)痛發(fā)送郵件引谜。這個(gè)世界牍陌,總是需要一些人踏踏實(shí)實(shí)地做一些事情,這種人值得尊敬员咽。所以毒涧,如果你覺(jué)得這個(gè)庫(kù)很好,對(duì)你有所裨益贝室,請(qǐng)移步到項(xiàng)目Home Page點(diǎn)一下右上角的Star契讲,謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滑频,一起剝皮案震驚了整個(gè)濱河市捡偏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌峡迷,老刑警劉巖霹琼,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凉当,居然都是意外死亡枣申,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門看杭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)忠藤,“玉大人,你說(shuō)我怎么就攤上這事楼雹∧:ⅲ” “怎么了尖阔?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)榨咐。 經(jīng)常有香客問(wèn)我介却,道長(zhǎng),這世上最難降的妖魔是什么块茁? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任齿坷,我火速辦了婚禮,結(jié)果婚禮上数焊,老公的妹妹穿的比我還像新娘永淌。我一直安慰自己,他們只是感情好佩耳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布遂蛀。 她就那樣靜靜地躺著,像睡著了一般干厚。 火紅的嫁衣襯著肌膚如雪李滴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天蛮瞄,我揣著相機(jī)與錄音悬嗓,去河邊找鬼。 笑死裕坊,一個(gè)胖子當(dāng)著我的面吹牛包竹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播籍凝,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼周瞎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了饵蒂?” 一聲冷哼從身側(cè)響起声诸,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎退盯,沒(méi)想到半個(gè)月后彼乌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渊迁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年慰照,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琉朽。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毒租,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出箱叁,到底是詐尸還是另有隱情墅垮,我是刑警寧澤惕医,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站算色,受9級(jí)特大地震影響抬伺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灾梦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一峡钓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斥废,春花似錦、人聲如沸给郊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)淆九。三九已至统锤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炭庙,已是汗流浹背饲窿。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留焕蹄,地道東北人逾雄。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像腻脏,于是被迫代替她去往敵國(guó)和親鸦泳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容