Python email.parser.BytesFeedParser调用close()报空列表索引错误
错误原因
你代码里有两个核心问题,直接触发了这个报错:
- Parser实例复用违规:你把
a = BytesFeedParser()的初始化放在了所有邮件遍历循环的外面,还把a.close()放在了单封邮件行遍历的末尾。也就是说第一次循环处理第一封邮件时,调用close()就已经把parser的内部解析栈清空、完成了这次解析流程,后续循环处理第二封及之后的邮件时,你还在往已经关闭的parser里feed数据,这正好踩中文档里说的「close()后调用feed()属于未定义行为」,内部状态错乱后就会触发pop from empty list的底层错误。 - 喂入数据格式不符合要求:POP3协议中
retr()返回的邮件内容行是剥离了末尾\r\n换行符的,而RFC规范要求邮件内容必须以CRLF作为行分隔符。你直接把不带换行符的单行字节喂给parser,解析器无法正确识别邮件头、MIME边界、正文的分隔位置,哪怕是单封邮件解析也会出现内容错乱、解析失败的问题。
另外你写的len(client.list()[1])放在循环条件里,会导致每次循环都向POP3服务器发送一次LIST命令,既浪费带宽也可能因为遍历过程中邮件数量变动触发索引错误。
修复方法
按照FeedParser的单次使用规则调整逻辑即可:
- 每解析一封邮件,就新建一个独立的BytesFeedParser实例,不要跨邮件复用
- 喂入每一行内容时,补上POP3接口剥离的
\r\n换行符 - 单封邮件的所有行全部喂入完成后,再调用close()获取解析结果
- 提前把邮件列表的查询结果存到变量里,不要在循环条件里重复请求服务器
修复后的可运行代码:
import poplib from email.parser import BytesFeedParser client = poplib.POP3(host=theServer) client.user(user=user) client.pass_(pswd=password) # 提前获取邮件总数,避免重复发送LIST请求 _, mail_id_list, _ = client.list() total_mail_count = len(mail_id_list) parsed_mails = [] for mail_idx in range(1, total_mail_count + 1): # 每封邮件单独初始化解析器 mail_parser = BytesFeedParser() _, mail_content_lines, _ = client.retr(mail_idx) for line in mail_content_lines: # 补上POP3返回时去掉的CRLF行分隔符 mail_parser.feed(line + b'\r\n') # 所有内容喂完再关闭解析器,得到完整的邮件对象 mail_obj = mail_parser.close() parsed_mails.append(mail_obj) # 用完记得关闭POP3连接 client.quit()
如果不想逐行喂入,也可以直接把单封邮件的行拼接成完整字节串,用BytesParser一次性解析,写法更简洁:
from email.parser import BytesParser from email import policy # ... 前面POP3连接逻辑不变 for mail_idx in range(1, total_mail_count + 1): _, mail_content_lines, _ = client.retr(mail_idx) full_mail_bytes = b'\r\n'.join(mail_content_lines) # 指定policy.default可以直接得到符合现代API的EmailMessage对象 mail_obj = BytesParser(policy=policy.default).parsebytes(full_mail_bytes) parsed_mails.append(mail_obj)
内容的提问来源于stack exchange,提问作者Wendaolee
相关产品推荐
相关产品推荐

