本文介绍通过Python自带的email模块解析电子邮件的基本实现方法。

  这里阿猪假设你已经通过pop、imap等方式获取到了一封邮件,或者有一个现成的eml文件。下边的代码着重演示解析邮件内容过程中的基本实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import imaplib
from email.parser import Parser
from email.header import decode_header
import re
import datetime

imap_user = '用户名email地址'
imap_object = imaplib.IMAP4_SSL(port="993",host="imap.xxx.com") #如果使用非安全链接,请尝试使用imaplib的IMAP4方法
imap_object.login(imap_user, '密码或授权码')
imap_object.select('INBOX') #选择'收件箱'
typ, msg_ids = imap_object.search(None, 'ALL')

ids = msg_ids[0]
ret = ids.decode('utf-8')
message_id_list = ret.split()
int_mail_num = len(message_id_list)
print('收件箱中共有%s封邮件'%int_mail_num)

msg = msg_ids[0]
msg_list = msg.split()
#print('msg_list=',msg_list)
ids = msg_list[0] #选择第1封邮件
results, data = imap_object.fetch(ids, "(RFC822)")
imap_object.close() #关闭与imap服务器的连接
str_source = data[0][1].decode('UTF-8')
###############以上内容不是本文的重点,只是为了测试时保持连贯#############


msg_email = Parser().parsestr(str_source) #从string中读取邮件源文件,返回email.message格式的数据
#如果你是从eml文件中加载邮件源文件,请尝试通过email模块的message_from_file方法(未测试)
#import email
#fp = open("xxx.eml","r")
#msg_email = email.message_from_file(fp)

str_from = msg_email["from"] #提取发件人信息
#tuple_from = email.utils.parseaddr(str_from) #也可用email.utils中的parseaddr方法提取发件人名称和地址(收件人也一样)
#此处未使用该方法,是因为测试时发现解析email地址为空的email时会发生把名称解析为地址的错误
#str_from_name = tuple_from[0]
#str_from_address = tuple_from[1]
str_from_name = re.search(r'(?<=")[\s\S]*?(?=")',str_from).group() #通过正则表达式提取发件人名称(解码前)
str_from_address = re.search(r'(?<=<)[\s\S]*?(?=>)',str_from).group() #通过正则表达式提取发件人email地址
value, charset = decode_header(str_from_name)[0] #提取发件人名称的数据和编码信息
if charset: #如果指定了编码
str_from_name = value.decode(charset) #则使用email模块的decode方法解码
print(">>From:%s<%s>"%(str_from_name,str_from_address))

str_to = msg_email["to"] #提取收件人信息
str_to_name = re.search(r'(?<=")[\s\S]*?(?=")',str_to).group() #通过正则表达式提取收件人名称(解码前)
str_to_address = re.search(r'(?<=<)[\s\S]*?(?=>)',str_to).group() #通过正则表达式提取收件人email地址()
value, charset = decode_header(str_to_name)[0] #提取收件人名称的数据和编码信息
if charset: #如果指定了编码
str_to_name = value.decode(charset) #则使用email模块的decode方法解码
print(">>To:%s<%s>"%(str_to_name,str_to_address))

str_date = msg_email["date"] #提取日期
str_date = str_date.replace('GMT','+0000') #测试时遇到有GMT+0:00时区的邮件服务器不使用通用的时区格式
dtime_date = datetime.datetime.strptime(str_date, '%a, %d %b %Y %H:%M:%S %z') #将string格式的日期数据转换为含有时区信息的datetime
print('>>时间:%s'%dtime_date)

str_subject = msg_email["subject"] #提取邮件主题
value, charset = decode_header(str_subject)[0] #提取邮件主题的数据和编码信息
if charset: #如果指定了编码
str_subject = value.decode(charset) #则使用email模块的decode方法解码
print('>>邮件主题:%s'%str_subject)

def decode_mime(msg): #此处封装的目的是在循环处理MIME数据块时重复利用代码(找不到这段代码的出处了,日后找到再补上出处。)
if msg.is_multipart(): #如果 MIME数据的格式是multi-part
parts = msg.get_payload()
for part in parts: #则遍历挨个解析
decode_mime(part) #拆分出的单个数据块再次传入自定义函数decode_mime(相当于创建了一个新的实例,与当前的循环不冲突)
else: #处理每一个MIME数据块
str_content_type = msg.get_content_type() #当前数据块的Content-Type
#print('str_content_type=%s'%str_content_type)
str_charset = msg.get_content_charset(failobj=None) #当前数据块的编码信息
#print('str_charset=',str_charset)
if str_content_type in ('text/plain', 'text/html'): #这里只处理纯文本、html两类数据,关于附件和其他类型数据的处理方法,请继续询问度娘
bytes_content = msg.get_payload(decode=True) #读取当前数据块中的正文内容,返回bytes
str_content = bytes_content.decode(str_charset) #解码
print('>>邮件正文(%s):%s'%(str_content_type,str_content))
#目前的email普遍采用同样内容的一封邮件同时包含纯文本、html两种类型的MIME,所以解析出的邮件正文看起来像是重复的。可根据需要自行进一步处理。
# 原文发表自 Zhu's Blog https://yfzhu.cn/posts/phrasing-email-with-email-model/
#本代码只演示解析邮件内容过程中的基本实现方法,未进行过多的容错和封装处理,可根据需要自行进一步处理。
decode_mime(msg_email)