引言
本篇博文是一個我之前沒有想要去關注,但就是這樣看似輕鬆的事情,卻出現了很多的bug,我嘗試用了163,、QQ以及谷歌郵箱去發送郵件,中間遇到了很多波折,其中一根谷歌的小號郵箱也被封了,所以想在這裏總結下我的一些錯誤與筆記。
STMP協議介紹
對於Python來說,需要編寫腳本調用郵件服務器來發送郵件,使用的協議是SMTP。接收郵件,使用的協議是POP3和IMAP。這三種的區別與用處後面再提,其實在上述三種郵箱的使用中,差別並不是很大,比如說QQ郵箱的STMP / IMAP是共用一個授權碼,而谷歌郵箱則是按照端口劃分。
在python中,提供收發郵件的庫爲smtplib、poplib和imaplib,而我們常用的爲smtplib,下面爲stmplib的一些常規方法介紹:
方法 | 描述 |
---|---|
SMTP.set_debuglevel(level) | 設置輸出debug調試信息,默認不輸出 |
SMTP.docmd(cmd[, argstring]) | 發送一個命令到SMTP服務器 |
SMTP.connect([host[, port]]) | 連接到指定的SMTP服務器 |
SMTP.helo([hostname]) | 使用helo指令向SMTP服務器確認你的身份 |
SMTP.ehlo(hostname) | 使用ehlo指令像ESMTP(SMTP擴展)確認你的身份 |
SMTP.ehlo_or_helo_if_needed() | 如果在以前的會話連接中沒有提供ehlo或者helo指令,這個方法會調用ehlo()或helo() |
SMTP.has_extn(name) | 判斷指定名稱是否在SMTP服務器上 |
SMTP.verify(address) | 判斷郵件地址是否在SMTP服務器上 |
SMTP.starttls([keyfile[, certfile]]) | 使SMTP連接運行在TLS模式,所有的SMTP指令都會被加密 |
SMTP.login(user, password) | 登錄SMTP服務器 |
SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[]) | 發送郵件 from_addr:郵件發件人 to_addrs:郵件收件人 msg:發送消息 |
SMTP.quit() | 關閉SMTP會話 |
SMTP.close() | 關閉SMTP服務器連接 |
以上表格轉自https://blog.51cto.com/lizhenliang/1875330,那麼我們就可以寫一個範例如下:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
my_sender='[email protected]' # 發件人郵箱賬號
my_pass = 'xxxxxxxxxx' # 發件人郵箱密碼
my_user='[email protected]' # 收件人郵箱賬號,我這邊發送給自己
def mail():
ret=True
try:
msg=MIMEText('successful!','plain','utf-8')
msg['From']=formataddr(["FromRunoob",my_sender]) # 括號裏的對應發件人郵箱暱稱、發件人郵箱賬號
msg['To']=formataddr(["FK",my_user]) # 括號裏的對應收件人郵箱暱稱、收件人郵箱賬號
msg['Subject']="放假通知" # 郵件的主題,也可以說是標題
server=smtplib.SMTP_SSL("smtp.qq.com", 465) # 發件人郵箱中的SMTP服務器,端口是25
server.login(my_sender, my_pass) # 括號中對應的是發件人郵箱賬號、郵箱密碼
server.sendmail(my_sender,[my_user,],msg.as_string()) # 括號中對應的是發件人郵箱賬號、收件人郵箱賬號、發送郵件
server.quit() # 關閉連接
except Exception: # 如果 try 中的語句沒有執行,則會執行下面的 ret=False
ret=False
return ret
ret=mail()
if ret:
print("郵件發送成功")
else:
print("郵件發送失敗")
將其保存爲py文件,然後運行就能去郵箱內查看到當前郵件了,但自己發給自己並不能說明是遵循STMP協議才發送或收到,所以我們需要將其放入一個固定任務中,將收件人當成我們要傳的參數,然後檢測是否能收到郵件纔算是驗證成功,這裏的這種定時任務,可以簡單的使用django和celery達到想要的效果,並且django中也有提供內置的mail模塊,但沒有stmplib好用,感覺bug有點多省略很多東西,所以還是以stmplib來構造函數:
# settings.py文件中:
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
#MAIL_USE_DEBUG = True
#MAIL_USE_TLS = True
# EMAIL_HOST = 'smtp.maxhichina.com'
EMAIL_PORT = 25
# 發送郵件的郵箱
EMAIL_HOST_USER = '[email protected]'
# 在郵箱中設置的客戶端授權密碼
EMAIL_HOST_PASSWORD = 'xxxx'
# 收件人看到的發件人
# EMAIL_FROM = '[email protected]'
EMAIL_FROM = 'xxx<[email protected]>'
然後創建一個celery的模塊包,裏面的task.py文件寫下發郵件的腳本:
#from django.core.mail import send_mail
from celery.main import app
from django.conf import settings
import random
import smtplib
from email.mime.text import MIMEText
from email.header import Header
@app.task(name='send_verify_mail', bing=True)
def send_verify_mail(to_email, code):
sender = settings.EMAIL_HOST_USER
receiver = [to_email]
subject = '放假通知'
username = settings.EMAIL_HOST_USER
password = settings.EMAIL_HOST_PASSWORD
msg = MIMEText('hello code %s' % code,
_charset='utf-8') #中文需參數‘utf-8',單字節字符不需要
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = settings.EMAIL_FROM
msg['To'] = to_email
smtp = smtplib.SMTP_SSL()
smtp.connect(settings.EMAIL_HOST)
smtp.login(username, password)
smtp.sendmail(sender, receiver, msg.as_string())
smtp.quit()
然後後面如果要發送郵件,只要調用這個函數,並啓動celery就行了,但這裏就出現了非常多的坑是各種郵箱完全不相同的驗證,另外還有相關的HOST和PORT,這裏將關於POP3/STMP/IMAP三種對應端口的情況列成一張表格爲:
郵箱類型 | 服務器名稱 | 服務器地址 | SSL協議端端口 | 端口 |
QQ郵箱 | POP3 | pop.qq.com | 110 | |
SMTP | smtp.qq.com | 465 | 25 | |
IMAP | imap.qq.com | 993 | ||
163郵箱 | POP3 | pop.163.com | 110 | |
SMTP | smtp.163.com | 465 | 25 | |
IMAP | imap.163.com | 993 | ||
gmail郵箱 | POP3 | pop.gmail.com | 995 | |
SMTP | smtp.gmail.com | 587 / 465 | 25(我試驗是可以) | |
IMAP | imap.gmail.com | 993 | ||
yahoo郵箱 | POP3 | pop.mail.yahoo.com | 110 | |
SMTP | smtp.mail.yahoo.com | 25 | ||
IMAP | imap.mail.yahoo.com | 993 | ||
表中列舉到的,除了谷歌郵箱,其它都是用的國內的郵箱賬號,我們主要是看STMP協議這一項,如果不做設置,可能會出現如下的情況:
這裏就需要我們去開啓自己郵箱裏的相關設置,這裏我詳細說的是QQ和谷歌,因爲這兩個也是我試驗得最久的兩個了。
QQ郵箱設置
- 進入郵箱,點擊設置-賬戶
- 在賬戶欄目下,往下拉,找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服務所在位置
然後將上述兩個服務開啓,一般會提示要驗證綁定郵箱的一些東西,比如說向某個號碼發短信,或者是密保令牌獲取驗證碼,完成這其中的一步操作後才能看到QQ郵箱爲每個賬號設置的QQ授權碼,然後如果是作爲發信息,那麼有用的爲第二個,那麼代碼中可以修改爲:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.qq.com'
MAIL_USE_DEBUG = True
MAIL_USE_TLS = True
# EMAIL_HOST = 'smtp.maxhichina.com'
EMAIL_PORT = 465
# 發送郵件的郵箱
EMAIL_HOST_USER = '[email protected]'
# 在郵箱中設置的客戶端授權密碼
EMAIL_HOST_PASSWORD = 'xxx'
# 收件人看到的發件人
EMAIL_FROM = 'Tim<[email protected]>'
一般如果照上面這麼寫基本上是沒啥問題的,可能Email_port改成25也能發送郵件,那表示是未加密的網關,可能就會自動判別爲垃圾郵件了,如果是465的話更保險些。QQ郵箱還是沒有啥太大的坑,最多全部都是退信,但Gmail的套路就可能是一環接一環了。
谷歌郵箱設置
上述國內郵箱的設置還能用Foxmail軟件直接設置,QQ郵箱中就有一個很詳細的說明:Foxmail常用郵件客戶端軟件設置
gmail郵箱是沒有授權碼的,應該說國外的郵箱好像很少要授權碼,都是直接拿密碼當成授權碼用,但這個裏面的坑就多了。
其實我寫這篇文章的原因也是我在gmail郵箱上試了很長一段時間,原因是這個坑有點深,也可能我的搜索方式不對。當我直接用密碼房授權碼時,發送郵件的時候會報錯爲:
b"5.7.8 username or password not accept,那麼也能說明這個密碼是無效的。在此之前,還遇到的bug是newwork is unreachable,即是網絡達不到要求,那個是因爲之前用的阿里雲國內服務器,然後用的25網關,但找了半天,發現好像不適用,阿里雲的STMP服務地址爲:
所以在這裏提一下,因爲當時我是拿我自己服務器測試,發現谷歌連接不上了。但項目是在國外服務器上,所以我直接在國外服務器上把郵箱替換成谷歌郵箱,然後回到這個錯誤:b"5.7.8 username or password not accept
然後翻遍了Stack Overflow還有一些國外怎麼設置谷歌郵箱的帖子,發現了三步:
將【安全性較低的應用程式存儲權限】設爲【啓用】
我們可以進入https://myaccount.google.com/lesssecureapps界面將設置開起來,最好是用gmail瀏覽器已經進行過登錄,那麼就能跳過一些繁瑣的登錄。
開啓後,重新以STMP協議發送郵件,然後這個時候可能就會報錯爲:b"5.7.7,圖沒有截了,大致意思應該說是驗證密碼錯誤,也就是說開了這個後不能登錄上去,如果沒用,那麼就下一步。
解除人機驗證鎖定
進入https://accounts.google.com/b/0/DisplayUnlockCaptcha 頁面中,Google 可能會在您透過新的裝置或應用程式登入時,要求您完成這項額外步驟。如要授權存取,請按下方的 [繼續] 按鈕。然後我們就進入到了授權碼的選擇界面,
一路選是就OK了,之後可以再次嘗試,如果還是不成功,這裏報啥錯忘了,如果還是不成功,那麼就最後一步。
開啓兩步驗證
第三步也是最後一步,如果還是不行,那隻能採用這種冒險的方式,爲啥說冒險呢,如果真的要做這步,就很有可能被封號幾天,我當時是啥也不懂,看到有人這樣搞,然後我也去搞了一波,但結果就不太好了。
這一步需要我們進入網址:https://myaccount.google.com/signinoptions/two-step-verification/enroll-welcome?utm_source=google-account&utm_medium=web,然後手動去獲取一個授權碼,並將密碼設置爲授權碼的方式。
中間過程我就不再說了,當時配置的時候沒有截圖,這裏默認有五種可以選,有windows、mac、Android、iPhone還有其它,這裏選擇其它直接確定,就能拿到授權碼,然後把密碼改完後感覺挺好的,但第二天就被封號了,可能我一次申請的有點多,也可能是其它各種原因,所以奉勸看到這裏的朋友,別輕易嘗試這種,如果上述兩步沒用,那麼就可以考慮換郵箱了,下面是我的慘痛教訓:
然後是申請複查,天地良心,我還沒開始搞啥破壞呢。。。
中間還有 b"5.7.14、b"5.7.4、b"5.7.7,我記得其中分別是認證密碼錯誤,stmp連接錯誤等,如果走完上面兩步還出現這類問題,第一是看拼寫有沒有問題,如果和我上面寫的QQ格式基本一致,端口是587或者465沒用,那可以嘗試重新走一遍上述步驟,可能中間哪裏沒有開起來,或者按第三步來,但我實驗是被封了兩天。
總結
後來還嘗試了雅虎還有網易企業郵箱,我都不知道我爲什麼要試這麼多種郵箱,因爲最近一個項目是海外項目,老闆搞了各種郵箱,但一直想換,那沒辦法咯,我就只能陪着他換。企業郵箱的話就是要做域名解析,如果是阿里雲服務器的話,這裏就不再說了,直接後臺改就完事了。感覺這篇文章算一個科普性質的,科普我從錯誤裏的經驗,還有就是郵箱咋設置,我感覺現在啥郵箱都能搞一搞了。。。