python實現郵件接收、附件下載

周小董發表於2019-01-25

傳送郵件

SMTP協議

SMTP(Simple Mail Transfer Protocol)即簡單郵件傳輸協議,它是一組用於由源地址到目的地址傳送郵件的規則,由它來控制信件的中轉方式。SMTP協議屬於TCP/IP協議簇,它幫助每臺計算機在傳送或中轉信件時找到下一個目的地。通過SMTP協議所指定的伺服器,就可以把E-mail寄到收信人的伺服器上了。

import smtplib
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr

def send_email(from_addr, to_addr, subject, password):
    msg = MIMEText("郵件正文",'html','utf-8')
    msg['From'] = u'<%s>' % from_addr
    msg['To'] = u'<%s>' % to_addr
    msg['Subject'] = subject

    smtp = smtplib.SMTP_SSL('smtp.163.com', 465)
    smtp.set_debuglevel(1)
    smtp.ehlo("smtp.163.com")
    smtp.login(from_addr, password)
    smtp.sendmail(from_addr, [to_addr], msg.as_string())

if __name__ == "__main__":
    # 這裡的密碼是開啟smtp服務時輸入的客戶端登入授權碼,並不是郵箱密碼
    # 現在很多郵箱都需要先開啟smtp才能這樣傳送郵件
    send_email(u"from_addr",u"to_addr",u"主題",u"password")
郵箱 SMTP伺服器 SSL協議埠 非SSL協議埠
163 smtp.163.com 465或者994 25
qq smtp.qq.com 465或587 25

接收郵件

POP3和IMAP

POP是指郵局協議,目的是讓使用者可以訪問郵箱伺服器中的郵件,允許使用者從伺服器上把郵件儲存到本地主機(即自己的計算機)上,同時刪除儲存在郵件伺服器上的郵件,而POP3伺服器則是遵循POP3協議的接收郵件伺服器,用來接收電子郵件的。

後來又出現了IMAP協議(Interactive Mail Access Protocol),即互動式郵件訪問協議,與POP3的不同在於:開啟了IMAP後,在電子郵件客戶端收取的郵件仍然保留在伺服器上,同時在客戶端上的操作都會反饋到伺服器上,如:刪除郵件,標記已讀等,伺服器上的郵件也會做相應的動作。

  • poplib的常用方法:
方法 描述
POP3(server) 例項化POP3物件,server是pop伺服器地址
user(username) 傳送使用者名稱到伺服器,等待伺服器返回資訊
pass_(password) 密碼
stat() 返回郵箱的狀態,返回2元祖(訊息的數量,訊息的總位元組)
list([msgnum]) stat()的擴充套件,返回一個3元祖(返回資訊, 訊息列表, 訊息的大小),如果指定msgnum,就只返回指定訊息的資料
retr(msgnum) 獲取詳細msgnum,設定為已讀,返回3元組(返回資訊, 訊息msgnum的所以內容, 訊息的位元組數),如果指定msgnum,就只返回指定訊息的資料
dele(msgnum) 將指定訊息標記為刪除
quit() 登出,儲存修改,解鎖郵箱,結束連線,退出
from poplib import POP3

p = POP3('pop.163.com')
p.user('xxxxxxx@163.com')
p.pass_('xxxxxxxx')

p.stat()
...
p.quit()

使用IMAP

python中的imaplib包支援IMAP4

常用方法:

方法 描述
IMAP4(server) 與IMAP伺服器建立連線
login(user, pass) 使用者密碼登入
list() 檢視所有的資料夾(IMAP可以支援建立資料夾)
select() 選擇資料夾預設是"INBOX"
search() 三個引數,第一的是CHARSET,通常為None(ASCII),第二個引數不知到是幹什麼官方沒解釋
import getpass, imaplib

M = imaplib.IMAP4()
M.login(getpass.getuser(), getpass.getpass())
M.select()
typ, data = M.search(None, 'ALL')
for num in data[0].split():
    typ, data = M.fetch(num, '(RFC822)')
    print 'Message %s\n%s\n' % (num, data[0][1])
M.close()
M.logout()

郵箱: 網易163郵箱,或qq郵箱

# -*- coding: utf-8 -*-
import poplib,email,telnetlib
import datetime,time,sys,traceback
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr


class down_email():

    def __init__(self,user,password,eamil_server):
        # 輸入郵件地址, 口令和POP3伺服器地址:
        self.user = user
        # 此處密碼是授權碼,用於登入第三方郵件客戶端
        self.password = password
        self.pop3_server = eamil_server

    # 獲得msg的編碼
    def guess_charset(self,msg):
        charset = msg.get_charset()
        if charset is None:
            content_type = msg.get('Content-Type', '').lower()
            pos = content_type.find('charset=')
            if pos >= 0:
                charset = content_type[pos + 8:].strip()
        return charset

    #獲取郵件內容
    def get_content(self,msg):
        content=''
        content_type = msg.get_content_type()
        # print('content_type:',content_type)
        if content_type == 'text/plain': # or content_type == 'text/html'
            content = msg.get_payload(decode=True)
            charset = self.guess_charset(msg)
            if charset:
                content = content.decode(charset)
        return content

    # 字元編碼轉換
    # @staticmethod
    def decode_str(self,str_in):
        value, charset = decode_header(str_in)[0]
        if charset:
            value = value.decode(charset)
        return value

    # 解析郵件,獲取附件
    def get_att(self,msg_in, str_day):
        attachment_files = []
        for part in msg_in.walk():
            # 獲取附件名稱型別
            file_name = part.get_param("name")  # 如果是附件,這裡就會取出附件的檔名
            # file_name = part.get_filename() #獲取file_name的第2中方法
            # contType = part.get_content_type()
            if file_name:
                h = email.header.Header(file_name)
                # 對附件名稱進行解碼
                dh = email.header.decode_header(h)
                filename = dh[0][0]
                if dh[0][1]:
                    # 將附件名稱可讀化
                    filename = self.decode_str(str(filename, dh[0][1]))
                    # print(filename)
                    # filename = filename.encode("utf-8")
                # 下載附件
                data = part.get_payload(decode=True)
                # 在指定目錄下建立檔案,注意二進位制檔案需要用wb模式開啟
                att_file = open('./test/' + filename, 'wb')
                att_file.write(data)  # 儲存附件
                att_file.close()
                attachment_files.append(filename)
            else:
                # 不是附件,是文字內容
                print(self.get_content(part))
                # # 如果ture的話內容是沒用的
                # if not part.is_multipart():
                #     # 解碼出文字內容,直接輸出來就可以了。
                #     print(part.get_payload(decode=True).decode('utf-8'))

        return attachment_files

    def run_ing(self):
        str_day = str(datetime.date.today())# 日期賦值
        # 連線到POP3伺服器,有些郵箱伺服器需要ssl加密,可以使用poplib.POP3_SSL
        try:
            telnetlib.Telnet(self.pop3_server, 995)
            self.server = poplib.POP3_SSL(self.pop3_server, 995, timeout=10)
        except:
            time.sleep(5)
            self.server = poplib.POP3(self.pop3_server, 110, timeout=10)

        # server.set_debuglevel(1) # 可以開啟或關閉除錯資訊
        # 列印POP3伺服器的歡迎文字:
        print(self.server.getwelcome().decode('utf-8'))
        # 身份認證:
        self.server.user(self.user)
        self.server.pass_(self.password)
        # 返回郵件數量和佔用空間:
        print('Messages: %s. Size: %s' % self.server.stat())
        # list()返回所有郵件的編號:
        resp, mails, octets = self.server.list()
        # 可以檢視返回的列表類似[b'1 82923', b'2 2184', ...]
        print(mails)
        index = len(mails)
        for i in range(index, 0, -1):# 倒序遍歷郵件
        # for i in range(1, index + 1):# 順序遍歷郵件
            resp, lines, octets = self.server.retr(i)
            # lines儲存了郵件的原始文字的每一行,
            # 郵件的原始文字:
            msg_content = b'\r\n'.join(lines).decode('utf-8')
            # 解析郵件:
            msg = Parser().parsestr(msg_content)
            #獲取郵件的發件人,收件人, 抄送人,主題
            # hdr, addr = parseaddr(msg.get('From'))
            # From = self.decode_str(hdr)
            # hdr, addr = parseaddr(msg.get('To'))
            # To = self.decode_str(hdr)
            # 方法2:from or Form均可
            From = parseaddr(msg.get('from'))[1]
            To = parseaddr(msg.get('To'))[1]
            Cc=parseaddr(msg.get_all('Cc'))[1]# 抄送人
            Subject = self.decode_str(msg.get('Subject'))
            print('from:%s,to:%s,Cc:%s,subject:%s'%(From,To,Cc,Subject))
            # 獲取郵件時間,格式化收件時間
            date1 = time.strptime(msg.get("Date")[0:24], '%a, %d %b %Y %H:%M:%S')
            # 郵件時間格式轉換
            date2 = time.strftime("%Y-%m-%d",date1)
            if date2 < str_day:
                break # 倒敘用break
                # continue # 順敘用continue
            else:
                # 獲取附件
                attach_file=self.get_att(msg,str_day)
                print(attach_file)

        # 可以根據郵件索引號直接從伺服器刪除郵件:
        # self.server.dele(7)
        self.server.quit()


if __name__ == '__main__':
    #把列印內容輸出到檔案
    # origin = sys.stdout
    # f = open('./test/log.txt', 'w')
    # sys.stdout = f
    try:
        # 輸入郵件地址, 口令和POP3伺服器地址:
        user = '8284@163.com'
        # 此處密碼是授權碼,用於登入第三方郵件客戶端
        password = '8284'
        eamil_server = 'pop.163.com'
        # user='xinfei@.com'
        # password = 'f67h2'
        # eamil_server = 'pop.exmail.qq.com'
        email_class=down_email(user=user,password=password,eamil_server=eamil_server)
        email_class.run_ing()
    except Exception as e:
        import traceback
        ex_msg = '{exception}'.format(exception=traceback.format_exc())
        print(ex_msg)
        # traceback.print_exc()
    # sys.stdout = origin
    # f.close()

遇到的並且待驗證問題:

1、報錯pop3 ConnectionRefusedError: [WinError 10061] 由於目標計算機積極拒絕,無法連線。

一開始直接用的 server = poplib.POP3(pop3_server),所以連線偶爾報錯上面的資訊。

之後改成指定埠和超時時間之後就不再有報錯資訊了

server = poplib.POP3(pop3_server, 110, timeout=10)
or 
server = poplib.POP3_SSL(pop3_server, 995, timeout=10)

獲取信體部分

for part in msg.walk():
        # 如果ture的話內容是沒用的
        if not part.is_multipart():            
            print(part.get_payload(decode=True).decode('utf-8')) 
            # 解碼出文字內容,直接輸出來就可以了。

walk()函式能歷遍郵件所有部分,所以通常都把它放到for迴圈裡面使用。然後再使用is_multipart()函式來判斷內容是否有用,列印出有用內容最後用get_payload(decode=True).decode(‘utf-8’)解碼並且列印到控制檯。通常這個迴圈有兩次,第一次是單純的字串格式的,能在控制檯顯示出來的,第二次迴圈列印的是像HTML的格式,能在瀏覽器裡檢視,就像平時看到的郵件那樣。

官方文件:

參考:https://www.cnblogs.com/itogo/p/5910681.html
https://blog.csdn.net/u012209894/article/details/82384987

相關文章