使用python内置的smtplib可以利用smtp协议来发送邮件,本文是使用pop3和imap两种方式来接收邮件,并对邮件内容进行解析。
利用IMAP协议接收邮件
需要用到的库均为Python内置库:
import imapclient
import email
import email.parser
import email.policy
import base64
imapclient用来从邮件服务器下载邮件,email,email.parser和email.policy用来解析邮件内容,base64用来处理邮件的正文部分的解码。
若使用编程方式从邮件服务器下载邮件,需要先在邮件服务器上进行设置,允许以客户端的方式通过IMAP或POP3的方式来进行访问。一旦设置允许,服务器会生成一个授权码,每次通过程序进行访问时,必须使用该授权码进行服务器的登录验证。
使用imapclient检查邮件服务器上的邮件信息:
imap = imapclient.IMAPClient("imap.sina.com", ssl=True)
imap.login("用户名", "授权码")
all_folders = imap.list_folders()
inbox = imap.select_folder("INBOX")
print('%d messages in INBOX' % inbox[b'EXISTS'])
ids = imap.search(["all"])
print(ids)
选择SSL方式连接新浪免费邮箱的IMAP邮件服务器(imap.sina.com),连接后进行身份验证,在login函数中提供用户名和授权码,通过服务器的身份验证后,使用list_folders函数可以获得服务器存放不同邮件所使用的文件夹,包括收件箱、草稿夹、已发送、垃圾邮件等。使用search函数可以按指定的条件进行邮件的搜索,搜索条件包括FROM(来自哪个发件人)、ON、BEFORE,SINCE(某个日期)、UNSEEN、SEEN(未读/已读)和ALL(所有邮件)。search函数的返回值是由列表盛放的符合条件的邮件ID值。
接下来就可以下载邮件:
data = imap.fetch(ids, ['BODY[]'])
fetch将查询出来的所有邮件(ids)的内容(["BODY[]"])都进行了下载。下载后以字典的形式存放到了data中。
如果想查看某一封邮件的内容,就要对data进行访问:
raw_message = data[19][b'BODY[]']
data的键是ids中邮件的id,data[19]就是获得ids中id为19的邮件内容。data[19]又是一个字典,键b'BODY[]'对应的内容,是邮件的主体内容。
之所以将邮件的主体内容存入raw_message变量中,是因为目前取到的主体内容是一个二进制的字符串。
接下来就要使用email,email.parser和email.policy对二进制主体内容进行解码操作:
msg = email.parser.BytesParser(policy=email.policy.default).parsebytes(raw_message)
解码时,创建了email.parser下的BytesParser对象,构造器中必须传入关键字参数policy = email.policy.default,官方文档说后期会将email.policy.default作为policy参数的默认值,但是具体何时还未给出。构建了BytesParser解析器后,使用parsebytes对二进制字符串raw_message进行解码。解码完成后会获得一个email.message.EmaiMessage对象,该对象重写了魔术方法___getitem__和__setitem__,所以我们能用类似访问字典的格式来访问EmailMessage中的内容:
print(f'邮件的发信人是:{msg["From"]}')
print(f'邮件的收信人是:{msg["To"]}')
print(f'邮件的主题是:{msg["Subject"]}')
print(f'邮件的日期是:{msg["Date"]}')
在获取了邮件的基本信息后,接下来去获取邮件的正文。邮件的正文可能存在两种格式,一种是富媒体格式一种是纯文本格式,根据MIME的主type可以进行区分,富媒体格式的主type是multipart,而纯文本的主type是text,根据不同的格式要采用不同方式的解析:
maintype = msg.get_content_maintype()
content = None
if maintype == 'multipart':
for part in msg.get_payload():
if part.get_content_type() == 'text/plain':
content = part.get_payload()
elif maintype == 'text':
content = msg.get_payload()
if content:
print(f'邮件的正文是:{base64.b64decode(content).decode("gbk")}')
邮件的正文是经过字符集编码后再经过Base64编码。所以,解码时就是先进行Base64解码再进行字符集解码。新浪邮箱中文电子邮件的编码字符集是gb18030,gb18030字符集与gb2312字符集完全兼容,与gbk字符集基本兼容。所以在字符集解码时使用gbk、gb2312或gb18030都可以。
完整代码:
import imapclient
import email
import email.parser
import email.policy
import base64
imap = imapclient.IMAPClient("imap.sina.com", ssl=True)
imap.login("用户名", "授权码")
all_folders = imap.list_folders()
print(all_folders)
inbox = imap.select_folder("INBOX")
print('%d messages in INBOX' % inbox[b'EXISTS'])
ids = imap.search(["all"])
print(ids)
data = imap.fetch(ids, ['BODY[]'])
raw_message = data[19][b'BODY[]']
msg = email.parser.BytesParser(policy=email.policy.default).parsebytes(raw_message)
print(f'邮件的发信人是:{msg["From"]}')
print(f'邮件的收信人是:{msg["To"]}')
print(f'邮件的主题是:{msg["Subject"]}')
print(f'邮件的日期是:{msg["Date"]}')
maintype = msg.get_content_maintype()
content = None
if maintype == 'multipart':
for part in msg.get_payload():
if part.get_content_type() == 'text/plain':
content = part.get_payload()
elif maintype == 'text':
content = msg.get_payload()
if content:
print(f'邮件的正文是:{base64.b64decode(content).decode("gbk")}')
利用POP3协议接收邮件
需要用到的库均为Python内置库:
import poplib
import email
import email.parser
import email.policy
import base64
poplib用来从邮件服务器下载邮件,email,email.parser和email.policy用来解析邮件内容,base64用来处理邮件的正文部分的解码。
通过使用的库就可以看出来,除了访问邮件服务器和下载邮件的方式与IMAP有所不同,邮件的解析部分大同小异。
使用poplib检查邮件服务器上的邮件信息:
p = poplib.POP3_SSL("pop.sina.com")
p.user('用户名')
p.pass_('授权码')
print(p.stat())
print(p.list())
选择SSL方式连接新浪免费邮箱的POP3邮件服务器(pop.sina.com),连接后进行身份验证,需要注意的是为了避免与Python关键字pass冲突,所以poplib提供授权码的函数名称是pass_。通过服务器的身份验证后,使用stat函数可以获得用元组形式表示的当前邮件服务器上有几封邮件以及邮件的总字节数。list函数以列表的形式返回比较详细的邮件信息:
['服务器响应内容', [b'邮件编号 字节数',b'邮件编号 字节数',b'邮件编号 字节数',...], 字节数]
接下来,如果要下载邮件,就需要从list函数返回的列表第2项中,解析出邮件的编号,根据邮件的编号才可以下载:
message = p.list()[1][0]
number, size = message.decode('ascii').split()
list()函数列表的第二项也是个列表,列表中的所有内容都是经过ascii字符集编码的二进制,所以要进行解码后再去获得文本形式的邮件编号number。
有了邮件编号number,就可以下载该邮件:
resp, lines, octet = p.retr(number)
retr函数与list函数类似,返回的是列表形式的内容,我们感兴趣的只是列表中第二项的邮件内容,所以接下来的解析就是围绕变量lines展开的。
raw_message = b''
for line in lines:
doc += line
doc += b'\r\n'
前面使用imapclient获取邮件内容时,通过邮件编号和键b'BODY[]'获得的邮件内容是一个二进制的字符串,但是poplib的retr获取的邮件内容lines是一个列表,它把邮件内容的各个部分拆分成了列表列表中的一个个列表项,所以我们要手动的lines列表中的这些内容拼接成一个二进制的字符串。每拼接一部分内容要加一个换行符。
一旦就代表邮件内容的二进制字符串拼接完毕,解析的工作与之前完全一样:
msg = email.parser.BytesParser(policy=email.policy.default).parsebytes(doc)
print(f'邮件的发信人是:{msg["From"]}')
print(f'邮件的收信人是:{msg["To"]}')
print(f'邮件的主题是:{msg["Subject"]}')
print(f'邮件的日期是:{msg["Date"]}')
maintype = msg.get_content_maintype()
content = None
if maintype == 'multipart':
for part in msg.get_payload():
if part.get_content_type() == 'text/plain':
content = part.get_payload()
elif maintype == 'text':
content = msg.get_payload()
if content:
print(f'邮件的正文是:{base64.b64decode(content).decode("gbk")}')
最后,在使用poplib接受和解析完邮件后,一定要记得使用quit函数退出:
p.quit()
完整代码:
import poplib
import email
import email.parser
import email.policy
import base64
p = poplib.POP3_SSL("pop.sina.com")
p.user('用户名')
p.pass_('授权码')
print(p.stat())
print(p.list())
message = p.list()[1][0]
number, size = message.decode('ascii').split()
resp, lines, octet = p.retr(number)
doc = b''
for line in lines:
doc += line
doc += b'\r\n'
msg = email.parser.BytesParser(policy=email.policy.default).parsebytes(doc)
print(f'邮件的发信人是:{msg["From"]}')
print(f'邮件的收信人是:{msg["To"]}')
print(f'邮件的主题是:{msg["Subject"]}')
print(f'邮件的日期是:{msg["Date"]}')
maintype = msg.get_content_maintype()
content = None
if maintype == 'multipart':
for part in msg.get_payload():
if part.get_content_type() == 'text/plain':
content = part.get_payload()
elif maintype == 'text':
content = msg.get_payload()
if content:
print(f'邮件的正文是:{base64.b64decode(content).decode("gbk")}')
p.quit()