Python 实战:内网渗透中的信息收集自动化脚本(6)
用途限制声明,本文仅用于网络安全技术研究、教育与知识分享。文中涉及的渗透测试方法与工具,严禁用于未经授权的网络攻击、数据窃取或任何违法活动。任何因不当使用本文内容导致的法律后果,作者及发布平台不承担任何责任。渗透测试涉及复杂技术操作,可能对目标系统造成数据损坏、服务中断等风险。读者需充分评估技术能力与潜在后果,在合法合规前提下谨慎实践。
这次我们主要讨论使用python脚本来读取本地邮箱的重要文件或者信息,代码能够根据不同邮箱的默认路径进行读取,并且根据不同格式进行解析,并且如果默认路径找不到,可以手动进行输入,代码如下
import os
import glob
import mailbox
import sqlite3
from email.header import decode_header
from libratom.lib.pff import PffArchive
from datetime import datetimeclass EnhancedEmailReader:"""增强版本地邮件读取器,支持自动查找和手动输入路径"""def __init__(self):# 本地邮箱客户端配置self.client_configs = {# 微软系"outlook": {"path": os.path.expanduser("~\\Documents\\Outlook Files\\"),"ext": ".pst","desc": "Outlook","reader": self.read_pst},"windows_mail": {"path": os.path.expanduser("~\\AppData\\Local\\Microsoft\\Windows Mail\\"),"ext": ".eml","desc": "Windows 邮件","reader": self.read_eml},# Mozilla系"thunderbird": {"path": os.path.expanduser("~\\AppData\\Roaming\\Thunderbird\\Profiles\\"),"ext": ".mbox","desc": "Thunderbird","reader": self.read_mbox},# 网易系"netease_mail_master": {"path": os.path.expanduser("~\\AppData\\Roaming\\163 Mail\\Data\\"),"ext": ".db","desc": "网易邮箱大师","reader": self.read_netease_db},"netease_163_client": {"path": os.path.expanduser("~\\AppData\\Local\\Netease\\163mail\\User Data\\"),"ext": ".eml","desc": "163邮箱客户端","reader": self.read_eml},# QQ系"qq_mail": {"path": os.path.expanduser("~\\AppData\\Roaming\\Tencent\\QQMail\\Data\\"),"ext": ".db","desc": "QQ邮箱客户端","reader": self.read_qq_db},"foxmail": { # 腾讯旗下"path": os.path.expanduser("~\\AppData\\Roaming\\Foxmail\\Profiles\\"),"ext": ".eml","desc": "Foxmail","reader": self.read_eml},# 其他"sina_mail": {"path": os.path.expanduser("~\\AppData\\Roaming\\SinaMail\\Data\\"),"ext": ".eml","desc": "新浪邮箱","reader": self.read_eml}}def decode_str(self, s):"""解码邮件中的特殊字符"""if not s:return ""try:decoded = decode_header(s)result = []for part, encoding in decoded:if isinstance(part, bytes):result.append(part.decode(encoding or "utf-8", errors="replace"))else:result.append(str(part))return "".join(result)except:return str(s)# 通用格式读取方法def read_pst(self, file_path):"""读取Outlook的PST文件"""try:archive = PffArchive(file_path)print(f"\n===== 处理 {os.path.basename(file_path)} =====")for folder in archive.folders():msg_count = folder.get_number_of_sub_messages()if msg_count > 0:print(f"文件夹: {folder.name} (共 {msg_count} 封邮件)")for i, msg in enumerate(folder.sub_messages, 1):print(f"\n邮件 {i}/{msg_count}")print(f"发件人: {self.decode_str(msg.get_sender_name())}")print(f"主题: {self.decode_str(msg.get_subject())}")print(f"时间: {msg.get_delivery_time() or '未知'}")print(f"正文: {self.decode_str(msg.get_plain_text_body() or '无正文')[:500]}...")except Exception as e:print(f"PST文件处理错误: {str(e)}")def read_mbox(self, file_path):"""读取MBOX格式文件(Thunderbird等)"""try:mbox = mailbox.mbox(file_path)msg_count = len(mbox)print(f"\n===== 处理 {os.path.basename(file_path)} =====")print(f"共发现 {msg_count} 封邮件")for i, msg in enumerate(mbox, 1):sender = self.decode_str(msg.get("from", ""))subject = self.decode_str(msg.get("subject", ""))date = self.decode_str(msg.get("date", ""))body = self._extract_body(msg)print(f"\n邮件 {i}/{msg_count}")print(f"发件人: {sender}")print(f"主题: {subject}")print(f"时间: {date}")print(f"正文: {body[:500]}...")except Exception as e:print(f"MBOX文件处理错误: {str(e)}")def read_eml(self, file_path):"""读取EML格式文件(通用单封邮件格式)"""try:with open(file_path, "rb") as f:msg = mailbox.mboxMessage(f)print(f"\n===== 处理 {os.path.basename(file_path)} =====")print(f"发件人: {self.decode_str(msg.get('from', ''))}")print(f"收件人: {self.decode_str(msg.get('to', ''))}")print(f"主题: {self.decode_str(msg.get('subject', ''))}")print(f"时间: {self.decode_str(msg.get('date', ''))}")print(f"正文: {self._extract_body(msg)[:500]}...")except Exception as e:print(f"EML文件处理错误: {str(e)}")# 网易邮箱特殊格式处理def read_netease_db(self, file_path):"""读取网易邮箱大师的.db数据库文件"""try:print(f"\n===== 处理网易邮箱数据库 {os.path.basename(file_path)} =====")conn = sqlite3.connect(file_path)cursor = conn.cursor()# 网易邮箱数据库表结构分析try:# 获取邮件列表cursor.execute("SELECT id, subject, fromaddr, sendtime, content FROM mail")mails = cursor.fetchall()print(f"共发现 {len(mails)} 封邮件")for i, mail in enumerate(mails, 1):mail_id, subject, fromaddr, sendtime, content = mail# 转换时间戳try:send_time = datetime.fromtimestamp(int(sendtime)/1000).strftime('%Y-%m-%d %H:%M:%S')except:send_time = "未知时间"print(f"\n邮件 {i}/{len(mails)}")print(f"发件人: {self.decode_str(fromaddr)}")print(f"主题: {self.decode_str(subject)}")print(f"时间: {send_time}")print(f"正文: {self.decode_str(content)[:500]}...")except Exception as e:print(f"数据库查询错误: {str(e)}")print("尝试备用表结构查询...")try:cursor.execute("SELECT id, title, sender, createtime, text FROM email")mails = cursor.fetchall()print(f"共发现 {len(mails)} 封邮件")for i, mail in enumerate(mails, 1):mail_id, title, sender, createtime, text = mailtry:send_time = datetime.fromtimestamp(int(createtime)/1000).strftime('%Y-%m-%d %H:%M:%S')except:send_time = "未知时间"print(f"\n邮件 {i}/{len(mails)}")print(f"发件人: {self.decode_str(sender)}")print(f"主题: {self.decode_str(title)}")print(f"时间: {send_time}")print(f"正文: {self.decode_str(text)[:500]}...")except:print("无法识别的数据库结构,请手动查看")conn.close()except Exception as e:print(f"网易邮箱数据库处理错误: {str(e)}")# QQ邮箱特殊格式处理def read_qq_db(self, file_path):"""读取QQ邮箱客户端的.db数据库文件"""try:print(f"\n===== 处理QQ邮箱数据库 {os.path.basename(file_path)} =====")conn = sqlite3.connect(file_path)cursor = conn.cursor()# QQ邮箱数据库表结构分析try:# 获取邮件列表(不同版本表名可能不同)cursor.execute("SELECT msgId, subject, fromAddress, date, textContent FROM Message")mails = cursor.fetchall()print(f"共发现 {len(mails)} 封邮件")for i, mail in enumerate(mails, 1):msg_id, subject, from_addr, date, content = mail# 转换时间try:send_time = datetime.fromtimestamp(int(date)).strftime('%Y-%m-%d %H:%M:%S')except:send_time = "未知时间"print(f"\n邮件 {i}/{len(mails)}")print(f"发件人: {self.decode_str(from_addr)}")print(f"主题: {self.decode_str(subject)}")print(f"时间: {send_time}")print(f"正文: {self.decode_str(content)[:500]}...")except Exception as e:print(f"数据库查询错误: {str(e)}")print("尝试备用表结构查询...")try:cursor.execute("SELECT id, title, sender, time, content FROM mail")mails = cursor.fetchall()print(f"共发现 {len(mails)} 封邮件")for i, mail in enumerate(mails, 1):msg_id, title, sender, time, content = mailtry:send_time = datetime.fromtimestamp(int(time)).strftime('%Y-%m-%d %H:%M:%S')except:send_time = "未知时间"print(f"\n邮件 {i}/{len(mails)}")print(f"发件人: {self.decode_str(sender)}")print(f"主题: {self.decode_str(title)}")print(f"时间: {send_time}")print(f"正文: {self.decode_str(content)[:500]}...")except:print("无法识别的数据库结构,请手动查看")conn.close()except Exception as e:print(f"QQ邮箱数据库处理错误: {str(e)}")def _extract_body(self, msg):"""提取邮件正文"""body = ""try:if msg.is_multipart():for part in msg.walk():if part.get_content_type() == "text/plain" and "attachment" not in str(part.get("Content-Disposition")):body = self.decode_str(part.get_payload(decode=True) or b"")breakelse:body = self.decode_str(msg.get_payload(decode=True) or b"")return body.strip() or "无正文内容"except:return "无法解析正文"def find_all_email_files(self):"""查找系统中所有支持的邮箱文件"""all_files = []print("===== 开始扫描系统中的本地邮箱文件 =====")for client, config in self.client_configs.items():path = config["path"]ext = config["ext"]desc = config["desc"]if os.path.exists(path):# 递归查找所有匹配的文件files = glob.glob(f"{path}/**/*{ext}", recursive=True)if files:print(f"\n发现 {len(files)} 个{desc}文件:")for file in files[:5]: # 只显示前5个,避免输出过长print(f" - {file}")if len(files) > 5:print(f" ... 还有 {len(files)-5} 个文件未显示")all_files.extend([(file, config) for file in files])else:print(f"\n{desc}默认路径不存在: {path}")return all_filesdef get_reader_for_file(self, file_path):"""根据文件扩展名获取相应的读取器"""ext = os.path.splitext(file_path)[1].lower()for config in self.client_configs.values():if config["ext"] == ext:return config["reader"]# 如果没有找到精确匹配,尝试根据扩展名猜测if ext == ".pst":return self.read_pstelif ext == ".mbox":return self.read_mboxelif ext == ".eml":return self.read_emlelif ext == ".db":# 对于db文件,先尝试QQ邮箱格式,再尝试网易邮箱格式def try_both_readers(path):print("尝试以QQ邮箱格式读取...")try:self.read_qq_db(path)returnexcept:print("QQ邮箱格式读取失败,尝试以网易邮箱格式读取...")self.read_netease_db(path)return try_both_readerselse:return Nonedef handle_manual_input(self):"""处理用户手动输入文件路径"""while True:print("\n===== 手动输入文件路径 =====")file_path = input("请输入邮箱文件路径(直接回车返回主菜单): ").strip()if not file_path:return Falseif not os.path.exists(file_path):print(f"错误: 文件 '{file_path}' 不存在")continueif not os.path.isfile(file_path):print(f"错误: '{file_path}' 不是一个文件")continue# 获取合适的读取器reader = self.get_reader_for_file(file_path)if reader:reader(file_path)return Trueelse:print(f"不支持的文件格式: {os.path.splitext(file_path)[1]}")continuedef run(self):"""主运行函数"""print("===== 增强版本地邮箱读取器 =====")print("支持的邮箱客户端: " + ", ".join([v["desc"] for v in self.client_configs.values()]))while True:# 查找所有邮件文件email_files = self.find_all_email_files()if email_files:print(f"\n共发现 {len(email_files)} 个邮件文件")print("1. 处理找到的文件")print("2. 手动输入文件路径")print("3. 退出程序")choice = input("请选择操作(1/2/3): ").strip()if choice == "1":# 让用户选择要处理的文件print("\n请选择要处理的文件序号(输入数字,0返回):")display_count = min(10, len(email_files))for i in range(display_count):file_path, config = email_files[i]print(f" {i+1}. {os.path.basename(file_path)} ({config['desc']})")if len(email_files) > 10:print(f" ... 共 {len(email_files)} 个文件")try:file_choice = int(input("请选择: "))if file_choice == 0:continueif 1 <= file_choice <= len(email_files):file_path, config = email_files[file_choice-1]config["reader"](file_path)else:print("无效选择")except ValueError:print("请输入有效数字")elif choice == "2":self.handle_manual_input()elif choice == "3":print("程序已退出")breakelse:print("无效选择,请输入1、2或3")else:print("\n未找到任何支持的邮箱文件")choice = input("是否要手动输入文件路径? (y/n): ").lower()if choice == 'y' or choice == 'yes':if not self.handle_manual_input():print("程序已退出")breakelse:print("程序已退出")breakif __name__ == "__main__":reader = EnhancedEmailReader()reader.run()
一、类定义与初始化(EnhancedEmailReader
类及 __init__
方法)
这部分是整个程序的基础,定义了邮件读取器的核心配置。
class EnhancedEmailReader:"""增强版本地邮件读取器,支持自动查找和手动输入路径"""def __init__(self):# 本地邮箱客户端配置self.client_configs = {# 微软系"outlook": {"path": os.path.expanduser("~\\Documents\\Outlook Files\\"),"ext": ".pst","desc": "Outlook","reader": self.read_pst},# ... 其他邮箱客户端配置(省略)}
核心作用:
- 定义了一个
EnhancedEmailReader
类,作为整个邮件读取功能的载体。 - 在
__init__
方法中,通过self.client_configs
字典存储了主流邮箱客户端的配置信息,包括:path
:客户端默认存储路径(使用os.path.expanduser("~")
获取用户主目录,适配不同系统用户);ext
:邮件文件扩展名(如.pst
是 Outlook 专用格式);desc
:客户端中文描述(用于用户展示);reader
:对应的读取方法(每个格式有专属解析逻辑)。
二、编码解码工具(decode_str
方法)
邮件内容(如主题、发件人)常包含特殊编码(如 Base64、GBK),此方法用于统一解码。
def decode_str(self, s):"""解码邮件中的特殊字符"""if not s:return ""try:decoded = decode_header(s) # 解析带编码的字符串(如 "=?UTF-8?B?xxx?=")result = []for part, encoding in decoded:if isinstance(part, bytes):# 字节类型按指定编码解码,默认UTF-8,错误用"replace"避免崩溃result.append(part.decode(encoding or "utf-8", errors="replace"))else:result.append(str(part)) # 字符串直接拼接return "".join(result)except:return str(s) # 异常时返回原始字符串,保证程序不崩溃
核心作用:
- 利用
email.header.decode_header
解析邮件中带编码标记的字符串(如主题中的中文可能被编码为=?GB2312?B?...?=
); - 兼容字节和字符串类型,处理编码缺失或错误的情况,确保中文等特殊字符能正常显示。
三、邮件格式读取方法(核心功能)
针对不同邮箱文件格式(PST、MBOX、EML、DB 等),定义了专属读取方法,以下是典型示例:
1. PST 格式(Outlook):read_pst
方法
def read_pst(self, file_path):"""读取Outlook的PST文件"""try:archive = PffArchive(file_path) # 用libratom库解析PST文件print(f"\n===== 处理 {os.path.basename(file_path)} =====")for folder in archive.folders(): # 遍历PST中的文件夹msg_count = folder.get_number_of_sub_messages()if msg_count > 0:print(f"文件夹: {folder.name} (共 {msg_count} 封邮件)")for i, msg in enumerate(folder.sub_messages, 1): # 遍历邮件print(f"\n邮件 {i}/{msg_count}")print(f"发件人: {self.decode_str(msg.get_sender_name())}")print(f"主题: {self.decode_str(msg.get_subject())}")print(f"时间: {msg.get_delivery_time() or '未知'}")print(f"正文: {self.decode_str(msg.get_plain_text_body() or '无正文')[:500]}...")except Exception as e:print(f"PST文件处理错误: {str(e)}")
核心作用:
- 使用
libratom.lib.pff.PffArchive
解析 Outlook 专属的 PST 格式文件(PST 是二进制格式,需专用库解析); - 遍历 PST 中的文件夹和邮件,提取发件人、主题、时间、正文等核心信息,并用
decode_str
解码特殊字符; - 正文只显示前 500 字符(避免输出过长),并通过
try-except
捕获错误(如文件损坏)。
2. MBOX 格式(Thunderbird):read_mbox
方法
def read_mbox(self, file_path):"""读取MBOX格式文件(Thunderbird等)"""try:mbox = mailbox.mbox(file_path) # 用Python标准库mailbox解析MBOXmsg_count = len(mbox)print(f"\n===== 处理 {os.path.basename(file_path)} =====")print(f"共发现 {msg_count} 封邮件")for i, msg in enumerate(mbox, 1):sender = self.decode_str(msg.get("from", "")) # 从邮件头获取发件人subject = self.decode_str(msg.get("subject", "")) # 获取主题date = self.decode_str(msg.get("date", "")) # 获取时间body = self._extract_body(msg) # 调用辅助方法提取正文print(f"\n邮件 {i}/{msg_count}")print(f"发件人: {sender}")print(f"主题: {subject}")print(f"时间: {date}")print(f"正文: {body[:500]}...")except Exception as e:print(f"MBOX文件处理错误: {str(e)}")
核心作用:
- 利用 Python 标准库
mailbox.mbox
解析 MBOX 格式(Thunderbird 等客户端常用,本质是文本文件集合); - 通过邮件头(
msg.get("from")
等)提取元信息,调用_extract_body
方法提取正文。
3. EML 格式(通用单封邮件):read_eml
方法
def read_eml(self, file_path):"""读取EML格式文件(通用单封邮件格式)"""try:with open(file_path, "rb") as f:msg = mailbox.mboxMessage(f) # EML可直接用mailbox解析print(f"\n===== 处理 {os.path.basename(file_path)} =====")print(f"发件人: {self.decode_str(msg.get('from', ''))}")print(f"收件人: {self.decode_str(msg.get('to', ''))}") # EML通常包含收件人信息print(f"主题: {self.decode_str(msg.get('subject', ''))}")print(f"时间: {self.decode_str(msg.get('date', ''))}")print(f"正文: {self._extract_body(msg)[:500]}...")except Exception as e:print(f"EML文件处理错误: {str(e)}")
核心作用:
- EML 是单封邮件的通用格式(本质是符合邮件协议的文本文件),直接用
mailbox.mboxMessage
解析; - 相比 MBOX,EML 单独存储一封邮件,因此额外提取了收件人信息。
4. 数据库格式(网易 / QQ 邮箱客户端):read_netease_db
和 read_qq_db
方法
以网易邮箱为例:
def read_netease_db(self, file_path):"""读取网易邮箱大师的.db数据库文件"""try:print(f"\n===== 处理网易邮箱数据库 {os.path.basename(file_path)} =====")conn = sqlite3.connect(file_path) # 连接SQLite数据库cursor = conn.cursor()# 尝试主流表结构查询邮件try:cursor.execute("SELECT id, subject, fromaddr, sendtime, content FROM mail")mails = cursor.fetchall()print(f"共发现 {len(mails)} 封邮件")for i, mail in enumerate(mails, 1):mail_id, subject, fromaddr, sendtime, content = mail# 时间戳转换(网易邮箱时间戳通常是毫秒级)try:send_time = datetime.fromtimestamp(int(sendtime)/1000).strftime('%Y-%m-%d %H:%M:%S')except:send_time = "未知时间"print(f"\n邮件 {i}/{len(mails)}")print(f"发件人: {self.decode_str(fromaddr)}")print(f"主题: {self.decode_str(subject)}")print(f"时间: {send_time}")print(f"正文: {self.decode_str(content)[:500]}...")except:# 尝试备用表结构(应对客户端版本差异)cursor.execute("SELECT id, title, sender, createtime, text FROM email")# ... 后续逻辑类似conn.close()except Exception as e:print(f"网易邮箱数据库处理错误: {str(e)}")
核心作用:
- 网易 / QQ 邮箱客户端的邮件通常存储在 SQLite 数据库(.db 文件)中,因此用
sqlite3
库连接并查询; - 考虑到客户端版本差异(表名 / 字段可能不同),提供了多表结构查询的容错逻辑;
- 时间戳转换(客户端通常存储毫秒级时间戳,需转为秒级再格式化)。
四、辅助方法:_extract_body
提取邮件正文
邮件可能是 "多部分"(multipart,如包含文本 + 附件),此方法专门提取纯文本正文。
def _extract_body(self, msg):"""提取邮件正文"""body = ""try:if msg.is_multipart(): # 多部分邮件(可能包含正文、附件、HTML等)for part in msg.walk(): # 遍历所有部分# 只提取纯文本正文,排除附件if part.get_content_type() == "text/plain" and "attachment" not in str(part.get("Content-Disposition")):body = self.decode_str(part.get_payload(decode=True) or b"")break # 找到文本正文后退出else: # 单部分邮件(直接是纯文本)body = self.decode_str(msg.get_payload(decode=True) or b"")return body.strip() or "无正文内容"except:return "无法解析正文"
核心作用:
- 区分 "多部分邮件" 和 "单部分邮件",优先提取
text/plain
类型的内容(避免 HTML 格式或附件); - 用
part.get_payload(decode=True)
解码正文(可能被 Base64 等编码),并通过decode_str
处理字符编码。
五、文件查找与匹配:find_all_email_files
和 get_reader_for_file
1. 自动查找邮件文件:find_all_email_files
def find_all_email_files(self):"""查找系统中所有支持的邮箱文件"""all_files = []print("===== 开始扫描系统中的本地邮箱文件 =====")for client, config in self.client_configs.items():path = config["path"]ext = config["ext"]desc = config["desc"]if os.path.exists(path):# 递归查找所有匹配扩展名的文件(**表示子目录)files = glob.glob(f"{path}/**/*{ext}", recursive=True)if files:print(f"\n发现 {len(files)} 个{desc}文件:")for file in files[:5]: # 只显示前5个,避免输出过长print(f" - {file}")if len(files) > 5:print(f" ... 还有 {len(files)-5} 个文件未显示")all_files.extend([(file, config) for file in files])else:print(f"\n{desc}默认路径不存在: {path}")return all_files
核心作用:
- 遍历
client_configs
中定义的所有邮箱路径,用glob.glob
递归查找对应扩展名的文件(recursive=True
支持子目录搜索); - 收集所有找到的文件及对应的配置(用于后续调用专属读取方法),并向用户展示查找结果(限制显示数量,避免刷屏)。
2. 匹配文件读取器:get_reader_for_file
def get_reader_for_file(self, file_path):"""根据文件扩展名获取相应的读取器"""ext = os.path.splitext(file_path)[1].lower() # 获取扩展名(如 .pst)for config in self.client_configs.values():if config["ext"] == ext:return config["reader"] # 返回配置中对应的读取方法# 容错:如果没有精确匹配,按扩展名猜测if ext == ".pst":return self.read_pstelif ext == ".db":# .db可能是QQ或网易邮箱,定义一个尝试双格式的函数def try_both_readers(path):print("尝试以QQ邮箱格式读取...")try:self.read_qq_db(path)except:print("尝试以网易邮箱格式读取...")self.read_netease_db(path)return try_both_readers# ... 其他扩展名容错逻辑else:return None
核心作用:
- 根据文件扩展名自动匹配对应的读取方法(如
.pst
对应read_pst
); - 对模糊格式(如
.db
可能属于多个客户端)提供容错逻辑,尝试多种读取方法。
六、用户交互逻辑:handle_manual_input
和 run
方法
1. 手动输入文件路径:handle_manual_input
def handle_manual_input(self):"""处理用户手动输入文件路径"""while True:print("\n===== 手动输入文件路径 =====")file_path = input("请输入邮箱文件路径(直接回车返回主菜单): ").strip()if not file_path:return False # 回车返回if not os.path.exists(file_path):print(f"错误: 文件 '{file_path}' 不存在")continueif not os.path.isfile(file_path):print(f"错误: '{file_path}' 不是一个文件")continue# 调用匹配的读取器处理文件reader = self.get_reader_for_file(file_path)if reader:reader(file_path)return Trueelse:print(f"不支持的文件格式: {os.path.splitext(file_path)[1]}")
核心作用:
- 提供手动输入文件路径的交互界面,验证文件是否存在、是否为有效文件;
- 调用
get_reader_for_file
获取读取器并处理文件,支持用户灵活处理未被自动扫描到的文件。
2. 主运行逻辑:run
方法
def run(self):"""主运行函数"""print("===== 增强版本地邮箱读取器 =====")print("支持的邮箱客户端: " + ", ".join([v["desc"] for v in self.client_configs.values()]))while True:# 自动查找所有邮件文件email_files = self.find_all_email_files()if email_files:# 显示操作菜单print(f"\n共发现 {len(email_files)} 个邮件文件")print("1. 处理找到的文件")print("2. 手动输入文件路径")print("3. 退出程序")choice = input("请选择操作(1/2/3): ").strip()if choice == "1":# 让用户选择要处理的文件(显示前10个)print("\n请选择要处理的文件序号(输入数字,0返回):")display_count = min(10, len(email_files))for i in range(display_count):file_path, config = email_files[i]print(f" {i+1}. {os.path.basename(file_path)} ({config['desc']})")# ... 处理用户选择并调用读取器elif choice == "2":self.handle_manual_input() # 调用手动输入逻辑elif choice == "3":print("程序已退出")breakelse:# 未找到文件时,询问是否手动输入choice = input("是否要手动输入文件路径? (y/n): ").lower()if not (choice == 'y' or choice == 'yes'):print("程序已退出")break
核心作用:
- 程序入口逻辑,协调自动查找、用户选择、文件处理的全流程;
- 通过菜单式交互引导用户操作(处理自动找到的文件 / 手动输入 / 退出),提升易用性;
- 对未找到文件的场景提供容错,引导用户手动输入。
七、程序入口
if __name__ == "__main__":reader = EnhancedEmailReader()reader.run()
作用:当脚本直接运行时,创建 EnhancedEmailReader
实例并调用 run
方法启动程序。