使用python的email、smtplib、poplib模塊收發郵件
- 一封電子郵件的旅程是:
- MUA:Mail User Agent——郵件用戶代理。(即類似Outlook的電子郵件軟件)
- MTA:Mail Transfer Agent——郵件傳輸代理,就是那些Email服務提供商,比如網易、新浪等等。
- MDA:Mail Delivery Agent——郵件投遞代理。Email服務提供商的某個服務器
發件人 -> MUA -> MTA -> MTA -> 若干個MTA -> MDA <- MUA <- 收件人
-
要編寫程序來發送和接收郵件,本質上就是:
-
編寫MUA把郵件發到MTA;
-
編寫MUA從MDA上收郵件。
-
-
發郵件時,MUA和MTA使用的協議就是SMTP:Simple Mail Transfer Protocol,后面的MTA到另一個MTA也是用SMTP協議。
-
收郵件時,MUA和MDA使用的協議有兩種:POP:Post Office Protocol,目前版本是3,俗稱POP3;IMAP:Internet Message Access Protocol,目前版本是4,優點是不但能取郵件,還可以直接操作MDA上存儲的郵件,比如從收件箱移到垃圾箱,等等。
-
構造一個郵件對象就是一個Messag對象,如果構造一個MIMEText對象,就表示一個文本郵件對象,如果構造一個MIMEImage對象,就表示一個作為附件的圖片,要把多個對象組合起來,就用MIMEMultipart對象,而MIMEBase可以表示任何對象。它們的繼承關系如下:
Message
+- MIMEBase
+- MIMEMultipart
+- MIMENonMultipart+- MIMEMessage +- MIMEText +- MIMEImage
發送郵件
from email.mime.text import MIMEText # email模塊負責構造郵件 # 類email.mime.text.MIMEText(_text),是使用字符串_text來生成MIME對象的主體文本 # MIME是(Multipurpose Internet Mail Extensions) 多用途互聯網郵件擴展類型 # MIME是設置將某種擴展名文件用一種應用程序來打開的方式類型 # MIME設置的目的是為了在發送電子郵件時附加多媒體數據,讓郵件根據其類型進行處理。 import smtplib # smtplib模塊負責發送郵件 # 類smtplib.SMTP([host[, port[, local_hostname[, timeout]]]]) :SMTP對象 # 其中,host:smtp服務器主機名 # 其中,port:smtp服務器的端口,默認是25 # 如果在創建SMTP對象時定義了這兩個參數,在初始化時會自動調用connect方式連接服務器 # smtplib模塊還提供了SMTP_SSL類和LMTP類,對它們的操作與SMTP基本一致。 # SSL是一種安全傳輸,LMTP是與SMTP不同的另一種傳輸協議 from email.header import Header # 如果你想讓你的郵件標題使用非ASCII字符集,就要使用email.header編碼非ASCII字符集 # email.header.Header(s=None, charset=None, maxlinelen=None, header_name=None, continuation_ws=' ', errors='strict') # 創建一個能容納不同字符集的字符串的MIME對象的標頭 # 其中,s:初始標頭,即要編碼之前的標頭 # 其中,chatset:字符集,默認為ASCII # 其中,maxlinelen:標頭名的行的最大長度,默認為76 # 其中,header_name:標頭名,默認無 # 其中,continuation_ws:默認為單個空格字符 # 其中,errors:直接傳遞到Header的append()方法里 from email.utils import parseaddr, formataddr # email.utils:雜項工具 # email.utils.parseaddr(address):解析地址的功能, # 其中,address是一個包含用戶名和email地址的值(realname<address>),返回一個二元組(realname, email address) # email.utils.formataddr(pair, charset='utf-8') # 其中,pair是二元組(realname, email address) # 其中,charset是字符串,默認為utf-8 # 實際上,parseaddr(), formataddr(),兩者互逆 from email.mime.base import MIMEBase # email.mime.base.MIMEBase(_maintype, _subtype, **_params) # 這是MIME的一個基類。一般不需要在使用時創建實例。 # 其中_maintype是內容類型,如text或者image。 # _subtype是內容的minor type 類型,如plain或者gif。 # **_params是一個字典,直接傳遞給Message.add_header()。 from email.mime.multipart import MIMEMultipart # email.mime.multipart.MIMEMultipart(_subtype='mixed', boundary=None, _subparts=None, **_params) # MIMEMultipart是MIMEBase的一個子類,多個MIME對象的集合 # _subtype默認值為mixed。boundary是MIMEMultipart的邊界,默認邊界是可數的。 from email import encoders # email.encoders 功能是編碼器 def _format_addr(s): # 這個函數的作用是把一個標頭的用戶名編碼成utf-8格式的,如果不編碼原標頭中文用戶名,用戶名將無法被郵件解碼 name, addr = parseaddr(s) return formataddr((Header(name, "utf-8").encode(), addr)) # Header().encode(splitchars=';, \t', maxlinelen=None, linesep='\n') # 功能:編碼一個郵件標頭,使之變成一個RFC兼容的格式 # 先定義Email的地址,口令和SMTP服務器地址 from_addr = "x1@qq.com" password = input("請輸入發送郵箱的密碼:") to_addr = "x2@163.com" smtp_server = "smtp.qq.com" # 接下來定義郵件本身的內容 msg = MIMEMultipart() msg["From"] = _format_addr("發送者ReedSun <%s>" % from_addr) msg["To"] = _format_addr("接收者ReedSun <%s>" % to_addr) msg["Subject"] = Header("哈哈哈 這是一封測試信", "utf-8").encode() # 定義郵件正文 msg.attach(MIMEText("這是一封來自大魔王ReedSun,用Python發來的郵件", "plain", "utf-8")) # class email.mime.text.MIMEText(_text[, _subtype[, _charset]]):MIME文本對象, # 其中, _text是郵件內容, # 其中, _subtype郵件類型(MIME類型),可以是text/plain(普通文本郵件),html/plain(html郵件), # 其中, _charset編碼(charset:字符集),可以是gb2312等等。 # message.attch(payload) 將給定的附件或信息,添加到已有的有效附件或信息中,在調用之前必須是None或者List,調用后。payload將變成信息對象的列表 # 如果你想將payload設置成一個標量對象,要使用set_payload() with open('c:/work/test.jpg', 'rb') as f: mime = MIMEBase("image", "jpg", filename="test.jpg") mime.add_header("Content-Disposition", "attchment", filename="test.jpg") # add_header(_name, _value, **_params) 擴展標頭設置 # _name:要添加的標頭字段 # _value:標頭的內容 # Content-Disposition就是當用戶想把請求所得的內容存為一個文件的時候提供一個默認的文件名。 # 希望某類或者某已知MIME 類型的文件(比如:*.gif;*.txt;*.htm)能夠在訪問時彈出“文件下載”對話框。 mime.add_header("Content-ID", "<0>") mime.add_header("X-Attachment-Id", "0") mime.set_payload(f.read()) # set_payload(payload, charset=None) # 將附件添加到payload中 encoders.encode_base64(mime) # encoders.encode_base64(mime) 將payload內容編碼為base64格式 msg.attach(mime) # 接下來定義發送文件 server = smtplib.SMTP_SSL(smtp_server, 465) # qq郵箱使用SSL連接,端口為465 server.set_debuglevel(1) # SMTP.set_debuglevel(level):設置是否為調試模式。默認為False,即非調試模式, # 非調試模式:表示不輸出任何調試信息。 server.login(from_addr, password) # 登陸到smtp服務器。 # 現在幾乎所有的smtp服務器,都必須在驗證用戶信息合法之后才允許發送郵件。 server.sendmail(from_addr, [to_addr], msg.as_string()) # SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options]) :發送郵件。 # 這里要注意一下第三個參數,msg是字符串,表示郵件。 # 我們知道郵件一般由標題,發信人,收件人,郵件內容,附件等構成, # 發送郵件的時候,要注意msg的格式。這個格式就是smtp協議中定義的格式。 # 第二個參數,接受郵箱為一個列表,表示可以設置多個接受郵箱 # as_string()是MIMEMessage對象的一個方法,表示把MIMETest對象變成str server.quit() # 斷開與smtp服務器的連接,相當於發送"quit"指令。
接受郵件
from email.parser import Parser # email.parser 解析電子郵件 # 返回這個對象的email.message.Message實例 from email.header import decode_header from email.utils import parseaddr import poplib email = "x1@qq.com" password = input("密碼:") pop3_server = "pop.qq.com" # 連接到POP3服務器 server = poplib.POP3_SSL(pop3_server) # 注意qq郵箱使用SSL連接 # 打開調試信息 server.set_debuglevel(1) # 打印POP3服務器的歡迎文字 print(server.getwelcome().decode("utf-8")) # 身份認證 server.user(email) server.pass_(password) # stat()返回郵件數量和占用空間 print("信息數量:%s 占用空間 %s" % server.stat()) # list()返回(response, ['mesg_num octets', ...], octets),第二項是編號 resp, mails, octets = server.list() print("list!!!!",server.list()) # 返回的列表類似[b'1 82923', b'2 2184', ...] print(mails) # 獲取最新一封郵件,注意索引號從1開始 # POP3.retr(which) 檢索序號which的真個消息,然后設置他的出現標志 返回(response, ['line', ...], octets)這個三元組 index = len(mails) resp, lines, ocetes = server.retr(index) # lines 存儲了郵件的原始文本的每一行 # 可以獲得整個郵件的原始文本 msg_content = b"\r\n".join(lines).decode("utf-8") # 稍后解析出郵件 msg = Parser().parsestr(msg_content) # email.Parser.parsestr(text, headersonly=False) # 與parser()方法類似,不同的是他接受一個字符串對象而不是一個類似文件的對象 # 可選的headersonly表示是否在解析玩標題后停止解析,默認為否 # 返回根消息對象 # 關閉連接 server.quit() #### 解析郵件 # 郵件的Subject或者Email中包含的名字都是經過編碼后的str,要正常顯示,就必須decode def decode_str(s): value, charset = decode_header(s)[0] # decode_header()返回一個list,因為像Cc、Bcc這樣的字段可能包含多個郵件地址,所以解析出來的會有多個元素。上面的代碼我們偷了個懶,只取了第一個元素。 if charset: value = value.decode(charset) return value # 文本郵件的內容也是str,還需要檢測編碼,否則,非UTF-8編碼的郵件都無法正常顯示 def guess_charset(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 print_info(msg, indent=0): if indent ==0: for header in ["From", "To", "Subject"]: value = msg.get(header, "") if value: if header == "Subject": vaule = decode_str(value) else: hdr, addr =parseaddr(value) name = decode_str(hdr) value = u"%s <%s>" % (name, addr) print("%s%s:%s" % (" " * indent, header, value)) if (msg .is_multipart()): parts = msg.get_payload() for n, part in enumerate(parts): print('%spart %s' % (' ' * indent, n)) print('%s--------------------' % (' ' * indent)) print_info(part, indent + 1) else: content_type = msg.get_content_type() if content_type=='text/plain' or content_type=='text/html': content = msg.get_payload(decode=True) charset = guess_charset(msg) if charset: content = content.decode(charset) print('%sText: %s' % (' ' * indent, content + '...')) else: print('%sAttachment: %s' % (' ' * indent, content_type))
https://blog.csdn.net/weixin_35955795/article/details/52881044
https://blog.csdn.net/su749520/article/details/78885613