之前寫過(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è)附件的郵件钝吮。相比使用SMTP和email庫(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.png和test.pdf兩個(gè)文件在腳本同級(jí)目錄中,當(dāng)然也可以給出文件的絕對(duì)路徑)
發(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)于不同郵箱的host和post不同炉爆,網(wǎng)易的host是smtp.126.com和smtp.163.com, port都是25堕虹。而yagamil是為gmail設(shè)置的,所以參數(shù)默認(rèn)值為Google的郵箱服務(wù)芬首。
即使上面都設(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ò)誤信息,如下圖所示:
以.開頭的文件名在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=抛鳌!):
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契讲,謝謝。