1. 项目背景与需求
1.1 业务场景
在日常邮件处理工作中,经常需要对导出的 .eml 邮件文件进行信息修改,包括:
- 修改发件人(From)
- 修改收件人(To)
- 修改抄送人(Cc)
- 修改回复地址(Reply-To)
- 修改邮件日期(Date)
1.2 技术挑战
- 邮件头格式复杂,存在多行字段
- 部分邮件存在
Sender字段,需要与From同步修改 - 需要支持单文件和批量两种模式
- 最终交付物需方便非技术人员使用
2. 环境准备
2.1 安装 Python
- 访问 Python 官网:https://www.python.org/downloads/
- 下载 Python 3.8 或更高版本
- 安装时务必勾选 Add Python to PATH
2.2 验证安装
打开命令提示符(Win+R → cmd),执行:
python --version
预期输出:
Python 3.14.3
2.3 安装依赖库
本项目仅使用 Python 标准库,无需安装第三方依赖。
3. 第一阶段:单文件邮件修改工具
3.1 核心概念
邮件(.eml)是一种文本文件,包含邮件头和正文。邮件头由多个字段组成,格式如下:
From: sender@example.com
To: receiver@example.com
Date: Mon, 1 Jan 2024 09:00:00 +0800
Subject: Test Email
This is the email body...
3.2 字段匹配问题
邮件头中存在容易混淆的字段:
From: 发件人Sender: 实际发送者(代理发送时存在)Cc: 抄送人Reply-To: 回复地址
关键问题 :正则表达式 ^From:.*$ 会错误匹配 Sender: 字段。
解决方案:使用精确匹配,确保字段名后紧跟冒号。
3.3 单文件修改核心代码
import re
import shutil
from pathlib import Path
from datetime import datetime
WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
def modify_single_field(content, field, new_value):
"""修改单个邮件头字段(精确匹配)"""
# 根据字段名构建精确匹配的正则表达式
if field.lower() == 'from':
pattern = r'^From:(?:.*\n(?:[ \t].*\n)*)'
field_name = 'From'
elif field.lower() == 'to':
pattern = r'^To:(?:.*\n(?:[ \t].*\n)*)'
field_name = 'To'
elif field.lower() == 'cc':
pattern = r'^(Cc|CC):(?:.*\n(?:[ \t].*\n)*)'
field_name = 'Cc'
elif field.lower() == 'reply-to':
pattern = r'^Reply-To:(?:.*\n(?:[ \t].*\n)*)'
field_name = 'Reply-To'
else:
pattern = rf'^{field}:(?:.*\n(?:[ \t].*\n)*)'
field_name = field
new_field_line = f"{field_name}: {new_value}"
new_content = re.sub(pattern, new_field_line + '\n', content, count=1, flags=re.MULTILINE)
if new_content != content:
return new_content, f"{field_name} 修改为: {new_value}"
return content, f"未找到 {field_name}: 字段"
def modify_date_field(content, new_date_str):
"""修改日期字段"""
dt = parse_user_date(new_date_str)
weekday = WEEKDAYS[dt.weekday()]
new_date_line = f"Date: {weekday}, {dt.strftime('%d %b %Y %H:%M:%S')} +0800 (GMT+08:00)"
pattern = r'^Date:.*$'
new_content = re.sub(pattern, new_date_line, content, count=1, flags=re.MULTILINE)
if new_content != content:
return new_content, f"修改为 {dt.strftime('%Y-%m-%d %H:%M:%S')}"
return content, "未找到 Date 字段"
3.4 单文件修改主流程
def modify_single_file(eml_file):
"""单文件修改模式"""
# 创建备份
backup_path = eml_file.with_suffix('.eml.bak')
shutil.copy2(eml_file, backup_path)
# 读取文件
with open(eml_file, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 用户选择要修改的字段
modifications = {}
# ... 用户交互代码 ...
# 执行修改
for field, new_value in modifications.items():
if field == 'Date':
content, msg = modify_date_field(content, new_value)
else:
content, msg = modify_single_field(content, field, new_value)
# 写回文件
with open(eml_file, 'w', encoding='utf-8') as f:
f.write(content)
3.5 第一阶段成果
- 支持修改 From、To、Cc、Reply-To、Date
- 自动创建
.bak备份文件 - 精确匹配字段名,避免误匹配
- 支持多行字段(如抄送人多行)
4. 第二阶段:增加批量修改功能
4.1 批量修改的需求
在实际工作中,经常需要:
- 将整个文件夹的邮件发件人改成同一值
- 将整个文件夹的邮件日期统一调整
4.2 批量修改核心逻辑
def batch_modify_uniform(folder_path, field, new_value, recursive=True):
"""批量统一修改指定字段"""
folder = Path(folder_path)
# 查找所有 .eml 文件
if recursive:
eml_files = list(folder.rglob('*.eml'))
else:
eml_files = list(folder.glob('*.eml'))
print(f"找到 {len(eml_files)} 个 .eml 文件")
success = 0
for eml_file in eml_files:
with open(eml_file, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# 修改字段
if field == 'Date':
new_content, msg = modify_date_field(content, new_value)
else:
new_content, msg = modify_single_field(content, field, new_value)
if new_content != content:
# 备份并写入
backup_file(eml_file)
with open(eml_file, 'w', encoding='utf-8') as f:
f.write(new_content)
success += 1
print(f"完成! 成功: {success}/{len(eml_files)}")
4.3 Sender 字段同步问题
问题发现 :某些邮件存在 Sender 字段,修改 From 后,邮件客户端仍显示 Sender 的内容。
解决方案 :修改发件人时,同时修改 Sender 字段。
if field == 'From':
# 修改 From 字段
new_content, msg1 = modify_single_field(content, 'From', new_value)
# 修改 Sender 字段
new_content, msg2 = modify_single_field(content, 'Sender', new_value)
4.4 批量修改主菜单
def main():
print("请选择模式:")
print(" 1. 单文件修改")
print(" 2. 批量修改日期")
print(" 3. 批量修改发件人(同时修改 Sender)")
print(" 4. 批量修改收件人")
print(" 5. 批量修改抄送人")
print(" 6. 批量修改回复地址")
choice = input("请输入 (0-6): ")
if choice == '1':
# 单文件模式
path = input("请输入 .eml 文件路径: ")
modify_single_file(Path(path))
elif choice == '2':
# 批量修改日期
folder = input("请输入文件夹路径: ")
date = input("请输入目标日期: ")
batch_modify_uniform(folder, 'Date', date)
elif choice == '3':
# 批量修改发件人
folder = input("请输入文件夹路径: ")
new_from = input("请输入新的发件人: ")
batch_modify_uniform(folder, 'From', new_from)
# ... 其他模式 ...
4.5 第二阶段成果
- 支持批量修改文件夹内所有邮件
- 支持递归扫描子文件夹
- 发件人修改时自动同步 Sender 字段
- 保留单文件修改功能
- 统一的用户交互界面
5. 第三阶段:打包成可执行文件
5.1 为什么需要打包
非技术人员无法运行 Python 脚本,需要:
- 无需安装 Python
- 双击即可运行
- 界面简单友好
5.2 安装 PyInstaller
pip install pyinstaller
5.3 打包命令
# 基础打包(单文件)
pyinstaller --onefile --name "邮件修改工具" modify_email_header.py
# 带图标的打包
pyinstaller --onefile --name "邮件修改工具" --icon=email.ico modify_email_header.py
5.4 打包参数说明
| 参数 | 作用 |
|---|---|
--onefile |
打包成单个 .exe 文件 |
--name |
指定输出的文件名 |
--icon |
指定程序图标(.ico 格式) |
5.5 打包输出
dist/
└── 邮件修改工具.exe # 可执行文件
build/
└── ... # 临时文件(可删除)
modify_email_header.spec # 配置文件
5.6 文件大小
打包后的 exe 文件约 25-30 MB,可在 Windows 7/10/11 上直接运行。
5.7 第三阶段成果
- 生成独立的 .exe 可执行文件
- 无需安装 Python 环境
- 双击即可运行
- 可分发至任何 Windows 电脑
6. 完整源码
6.1 最终版本特性
| 功能 | 说明 |
|---|---|
| 单文件修改 | 修改单个邮件的 From/To/Cc/Reply-To/Date |
| 批量修改日期 | 文件夹内所有邮件统一日期 |
| 批量修改发件人 | 统一发件人,同时同步 Sender |
| 批量修改收件人 | 统一收件人 |
| 批量修改抄送人 | 统一抄送人(支持 Cc/CC) |
| 批量修改回复地址 | 统一回复地址 |
| 自动备份 | 修改前创建 .bak 备份 |
| 精确匹配 | 避免误匹配 Sender 等字段 |
| 多行支持 | 支持跨多行的邮件头字段 |
6.2 完整代码
1. 单文件邮件修改工具
#!/usr/bin/env python3
"""
单封邮件头信息修改工具
支持修改: From, To, Cc, Reply-To, Date
"""
import re
import shutil
from pathlib import Path
from datetime import datetime
# 星期缩写
WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
# 月份缩写映射
MONTHS = {
'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12
}
MONTHS_REV = {v: k for k, v in MONTHS.items()}
def backup_file(file_path):
"""创建备份"""
backup_path = file_path.with_suffix('.eml.bak')
if not backup_path.exists():
shutil.copy2(file_path, backup_path)
print(f"✅ 备份已创建: {backup_path.name}")
return backup_path
def format_date_line(dt: datetime) -> str:
"""格式化 Date 行"""
weekday = WEEKDAYS[dt.weekday()]
return f"Date: {weekday}, {dt.strftime('%d %b %Y %H:%M:%S')} +0800 (GMT+08:00)"
def parse_user_date(date_str: str) -> datetime:
"""解析用户输入的日期,支持多种格式"""
formats = [
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %H:%M',
'%Y-%m-%d',
'%d %b %Y %H:%M:%S',
'%d %b %Y %H:%M',
'%d %b %Y',
]
for fmt in formats:
try:
return datetime.strptime(date_str, fmt)
except ValueError:
continue
raise ValueError(f"无法解析日期格式: {date_str}")
def modify_date_field(content, new_date_str):
"""修改 Date 字段"""
try:
# 解析新日期
dt = parse_user_date(new_date_str)
new_date_line = format_date_line(dt)
# 匹配 Date 行(可能有时区差异)
pattern = r'^Date:.*$'
new_content = re.sub(pattern, new_date_line, content, count=1, flags=re.MULTILINE)
if new_content != content:
return new_content, f"修改为 {dt.strftime('%Y-%m-%d %H:%M:%S')}"
else:
# 没找到 Date 字段,尝试添加
header_end = content.find('\n\n')
if header_end == -1:
header_end = content.find('\r\n\r\n')
if header_end != -1:
new_content = content[:header_end] + new_date_line + '\n' + content[header_end:]
return new_content, f"添加并修改为 {dt.strftime('%Y-%m-%d %H:%M:%S')}"
else:
return None, "未找到 Date 字段且无法添加"
except Exception as e:
return None, str(e)
def modify_header_field(content, field, new_value):
"""
修改邮件头字段(支持多行)
field: 字段名,如 'From', 'To', 'Cc', 'Reply-To'
new_value: 新值,如果为 None 或空字符串则删除该字段
"""
# 处理字段名大小写
field_patterns = [field]
if field.lower() == 'cc':
field_patterns = ['Cc', 'CC']
for field_name in field_patterns:
# 匹配多行字段:字段名: 内容,可能跨越多行(续行以空格或制表符开头)
pattern = rf'^{field_name}:(?:.*\n(?:[ \t].*\n)*)'
if new_value:
new_field_line = f"{field_name}: {new_value}"
new_content = re.sub(pattern, new_field_line + '\n', content, count=1, flags=re.MULTILINE)
if new_content != content:
return new_content, f"{field_name}({field}) 修改为: {new_value}"
else:
# 删除该字段
new_content = re.sub(pattern, '', content, count=1, flags=re.MULTILINE)
if new_content != content:
return new_content, f"{field_name}({field}) 已删除"
# 没找到字段,尝试添加
if new_value:
header_end = content.find('\n\n')
if header_end == -1:
header_end = content.find('\r\n\r\n')
if header_end != -1:
new_line = f"{field}: {new_value}\n"
new_content = content[:header_end] + new_line + content[header_end:]
return new_content, f"{field}(添加) 为: {new_value}"
return content, f"未找到 {field}: 字段"
def modify_email_header(eml_path, modifications):
"""
修改邮件头字段
modifications: dict, 例如:
{
'From': '"New Name" <new@email.com>',
'To': 'new@recipient.com',
'Cc': 'cc@example.com',
'Reply-To': 'reply@example.com',
'Date': '2024-01-01 09:00:00'
}
"""
try:
with open(eml_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
original_content = content
changes = []
for field, new_value in modifications.items():
if new_value is None:
continue
if field == 'Date':
new_content, msg = modify_date_field(content, new_value)
if new_content:
content = new_content
changes.append(f"Date: {msg}")
else:
print(f" ❌ Date: {msg}")
else:
new_content, msg = modify_header_field(content, field, new_value)
if new_content != content:
content = new_content
changes.append(msg)
else:
print(f" ⚠️ {msg}")
if changes and content != original_content:
with open(eml_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"\n✅ 已修改:")
for change in changes:
print(f" • {change}")
return True
else:
print("❌ 没有字段被修改")
return False
except Exception as e:
print(f"❌ 错误: {e}")
return False
def show_preview(eml_file):
"""显示邮件头预览"""
print("\n当前邮件头预览:")
with open(eml_file, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
for i, line in enumerate(lines[:35]):
if line.strip():
# 截断过长的行
display_line = line.rstrip()
if len(display_line) > 100:
display_line = display_line[:100] + "..."
print(f" {display_line}")
if len(lines) > 35:
print(" ...")
def main():
print("=" * 60)
print("单封邮件头信息修改工具")
print("支持修改: From, To, Cc, Reply-To, Date")
print("=" * 60)
eml_path = input("\n请输入 .eml 文件路径: ").strip().strip('"')
eml_file = Path(eml_path)
if not eml_file.exists():
print(f"❌ 文件不存在: {eml_path}")
return
print("\n正在创建备份...")
backup_file(eml_file)
show_preview(eml_file)
print("\n" + "=" * 60)
print("可选修改的字段:")
print(" 1. From (发件人)")
print(" 2. To (收件人)")
print(" 3. Cc (抄送) - 支持 Cc 和 CC 两种格式")
print(" 4. Reply-To (回复地址)")
print(" 5. Date (日期) - 修改邮件列表显示的日期")
print(" 6. 全部修改")
print(" 0. 退出")
print("=" * 60)
modifications = {}
while True:
choice = input("\n请选择要修改的字段 (1-6, 0退出): ").strip()
if choice == '0':
break
elif choice == '1':
new_from = input("请输入新的 From 内容: ").strip()
if new_from:
modifications['From'] = new_from
print(f" From 将改为: {new_from}")
elif choice == '2':
new_to = input("请输入新的 To 内容: ").strip()
if new_to:
modifications['To'] = new_to
print(f" To 将改为: {new_to}")
elif choice == '3':
print("\n提示: Cc 字段可能是 'Cc:' 或 'CC:' 格式")
new_cc = input("请输入新的 Cc 内容 (留空删除): ").strip()
modifications['Cc'] = new_cc if new_cc else None
print(f" Cc 将改为: {new_cc if new_cc else '(删除)'}")
elif choice == '4':
new_reply = input("请输入新的 Reply-To 内容 (留空删除): ").strip()
modifications['Reply-To'] = new_reply if new_reply else None
print(f" Reply-To 将改为: {new_reply if new_reply else '(删除)'}")
elif choice == '5':
print("\n支持格式示例:")
print(" • 2024-01-01 09:00:00")
print(" • 2024-01-01")
print(" • 1 Jan 2024 09:00:00")
new_date = input("请输入新的日期时间: ").strip()
if new_date:
modifications['Date'] = new_date
print(f" Date 将改为: {new_date}")
elif choice == '6':
print("\n--- 修改 From ---")
new_from = input("新的 From (留空不变): ").strip()
if new_from:
modifications['From'] = new_from
print("\n--- 修改 To ---")
new_to = input("新的 To (留空不变): ").strip()
if new_to:
modifications['To'] = new_to
print("\n--- 修改 Cc (留空删除) ---")
print("提示: Cc 字段可能是 'Cc:' 或 'CC:' 格式")
new_cc = input("新的 Cc (留空删除): ").strip()
modifications['Cc'] = new_cc if new_cc else None
print("\n--- 修改 Reply-To (留空删除) ---")
new_reply = input("新的 Reply-To (留空删除): ").strip()
modifications['Reply-To'] = new_reply if new_reply else None
print("\n--- 修改 Date ---")
print("支持格式: 2024-01-01 09:00:00 或 1 Jan 2024 09:00:00")
new_date = input("新的 Date (留空不变): ").strip()
if new_date:
modifications['Date'] = new_date
break
else:
print("无效选项,请重新输入")
continue
more = input("\n是否继续修改其他字段? (y/n): ").strip().lower()
if more != 'y':
break
if modifications:
print("\n" + "=" * 60)
print("准备修改以下字段:")
for k, v in modifications.items():
if v is None:
print(f" {k}: (删除)")
else:
display_v = v[:80] + "..." if len(v) > 80 else v
print(f" {k}: {display_v}")
print("=" * 60)
confirm = input("\n确认修改? (y/n): ").strip().lower()
if confirm == 'y':
modify_email_header(eml_file, modifications)
print("\n修改完成!")
print("提示: 如需恢复,删除原文件,将 .bak 文件重命名为 .eml")
else:
print("已取消")
else:
print("未选择任何修改")
if __name__ == "__main__":
main()
2. 批量邮件修改工具
#!/usr/bin/env python3
"""
邮件头信息修改工具
支持: 单文件修改 + 批量修改(日期、发件人、收件人、抄送人、回复地址)
修复: 修改发件人时自动同步修改 Sender 字段
"""
import re
import shutil
from pathlib import Path
from datetime import datetime
# 星期缩写
WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
def backup_file(file_path):
"""创建备份"""
backup_path = file_path.with_suffix('.eml.bak')
if not backup_path.exists():
shutil.copy2(file_path, backup_path)
print(f"✅ 备份已创建: {backup_path.name}")
return backup_path
def format_date_line(dt: datetime) -> str:
"""格式化 Date 行"""
weekday = WEEKDAYS[dt.weekday()]
return f"Date: {weekday}, {dt.strftime('%d %b %Y %H:%M:%S')} +0800 (GMT+08:00)"
def parse_user_date(date_str: str) -> datetime:
"""解析用户输入的日期,支持多种格式"""
formats = [
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %H:%M',
'%Y-%m-%d',
'%d %b %Y %H:%M:%S',
'%d %b %Y %H:%M',
'%d %b %Y',
]
for fmt in formats:
try:
return datetime.strptime(date_str, fmt)
except ValueError:
continue
raise ValueError(f"无法解析日期格式: {date_str}")
def modify_date_field(content, new_date_str):
"""修改 Date 字段"""
try:
dt = parse_user_date(new_date_str)
new_date_line = format_date_line(dt)
pattern = r'^Date:.*$'
new_content = re.sub(pattern, new_date_line, content, count=1, flags=re.MULTILINE)
if new_content != content:
return new_content, f"修改为 {dt.strftime('%Y-%m-%d %H:%M:%S')}"
else:
header_end = content.find('\n\n')
if header_end == -1:
header_end = content.find('\r\n\r\n')
if header_end != -1:
new_content = content[:header_end] + new_date_line + '\n' + content[header_end:]
return new_content, f"添加并修改为 {dt.strftime('%Y-%m-%d %H:%M:%S')}"
return None, "未找到 Date 字段且无法添加"
except Exception as e:
return None, str(e)
def modify_single_field(content, field, new_value):
"""
修改单个邮件头字段(精确匹配)
返回: (新内容, 消息)
"""
# 根据字段名构建精确匹配的正则表达式
if field.lower() == 'from':
pattern = r'^From:(?:.*\n(?:[ \t].*\n)*)'
field_name = 'From'
elif field.lower() == 'to':
pattern = r'^To:(?:.*\n(?:[ \t].*\n)*)'
field_name = 'To'
elif field.lower() == 'cc':
pattern = r'^(Cc|CC):(?:.*\n(?:[ \t].*\n)*)'
field_name = 'Cc'
elif field.lower() == 'reply-to':
pattern = r'^Reply-To:(?:.*\n(?:[ \t].*\n)*)'
field_name = 'Reply-To'
elif field.lower() == 'sender':
pattern = r'^Sender:(?:.*\n(?:[ \t].*\n)*)'
field_name = 'Sender'
else:
pattern = rf'^{field}:(?:.*\n(?:[ \t].*\n)*)'
field_name = field
if new_value is not None:
new_field_line = f"{field_name}: {new_value}"
new_content = re.sub(pattern, new_field_line + '\n', content, count=1, flags=re.MULTILINE)
if new_content != content:
return new_content, f"{field_name} 修改为: {new_value}"
else:
# 没找到字段,尝试添加
header_end = content.find('\n\n')
if header_end == -1:
header_end = content.find('\r\n\r\n')
if header_end != -1:
new_line = f"{field_name}: {new_value}\n"
new_content = content[:header_end] + new_line + content[header_end:]
return new_content, f"{field_name}(添加) 为: {new_value}"
return content, f"未找到 {field_name}: 字段"
else:
# 删除字段
new_content = re.sub(pattern, '', content, count=1, flags=re.MULTILINE)
if new_content != content:
return new_content, f"{field_name} 已删除"
return content, f"未找到 {field_name}: 字段"
def modify_email_header(eml_path, modifications):
"""执行邮件头修改(单文件模式)"""
try:
with open(eml_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
original_content = content
changes = []
for field, new_value in modifications.items():
if new_value is None:
continue
if field == 'Date':
new_content, msg = modify_date_field(content, new_value)
if new_content:
content = new_content
changes.append(f"Date: {msg}")
else:
print(f" ❌ Date: {msg}")
else:
new_content, msg = modify_single_field(content, field, new_value)
if new_content != content:
content = new_content
changes.append(msg)
else:
print(f" ⚠️ {msg}")
if changes and content != original_content:
with open(eml_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"\n✅ 已修改:")
for change in changes:
print(f" • {change}")
return True
else:
print("❌ 没有字段被修改")
return False
except Exception as e:
print(f"❌ 错误: {e}")
return False
def show_preview(eml_file):
"""显示邮件头预览"""
print("\n当前邮件头预览:")
with open(eml_file, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
for i, line in enumerate(lines[:35]):
if line.strip():
display_line = line.rstrip()
if len(display_line) > 100:
display_line = display_line[:100] + "..."
print(f" {display_line}")
if len(lines) > 35:
print(" ...")
def batch_modify_uniform(folder_path, field, new_value, recursive=True):
"""
批量统一修改指定字段
注意: 修改发件人(From)时会自动同步修改 Sender 字段
"""
folder = Path(folder_path)
if not folder.exists():
print(f"❌ 文件夹不存在: {folder_path}")
return
if recursive:
eml_files = list(folder.rglob('*.eml'))
else:
eml_files = list(folder.glob('*.eml'))
if not eml_files:
print(f"❌ 未找到 .eml 文件: {folder_path}")
return
field_names = {
'From': '发件人',
'To': '收件人',
'Cc': '抄送人',
'Reply-To': '回复地址',
'Date': '日期'
}
print(f"找到 {len(eml_files)} 个 .eml 文件")
# 如果是修改发件人,需要同时修改 Sender
if field == 'From':
print(f"批量修改发件人 → {new_value}")
print(f"同时修改 Sender → {new_value}")
else:
print(f"批量修改{field_names.get(field, field)} → {new_value}")
print("-" * 50)
success = 0
failed = 0
skipped = 0
for i, eml_file in enumerate(eml_files, 1):
print(f"[{i}/{len(eml_files)}] {eml_file.name}")
try:
with open(eml_file, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
original_content = content
if field == 'From':
# 修改 From 字段
new_content, msg1 = modify_single_field(content, 'From', new_value)
if new_content != content:
content = new_content
print(f" ✅ {msg1}")
else:
print(f" ⚠️ {msg1}")
# 修改 Sender 字段(如果存在)
new_content, msg2 = modify_single_field(content, 'Sender', new_value)
if new_content != content:
content = new_content
print(f" ✅ {msg2}")
elif field == 'Date':
new_content, msg = modify_date_field(content, new_value)
if new_content and new_content != content:
content = new_content
print(f" ✅ {msg}")
else:
print(f" ⚠️ {msg}")
else:
new_content, msg = modify_single_field(content, field, new_value)
if new_content != content:
content = new_content
print(f" ✅ {msg}")
else:
print(f" ⚠️ {msg}")
if content != original_content:
backup_file(eml_file)
with open(eml_file, 'w', encoding='utf-8') as f:
f.write(content)
success += 1
else:
skipped += 1
except Exception as e:
print(f" ❌ 错误: {e}")
failed += 1
print("-" * 50)
print(f"完成! 成功: {success}, 跳过: {skipped}, 失败: {failed}")
def modify_single_file(eml_file):
"""单文件修改模式"""
print("\n" + "=" * 60)
print("单文件修改模式")
print("=" * 60)
if not eml_file.exists():
print(f"❌ 文件不存在: {eml_file}")
return
print("\n正在创建备份...")
backup_file(eml_file)
show_preview(eml_file)
print("\n" + "=" * 60)
print("可选修改的字段:")
print(" 1. From (发件人) - 同时修改 Sender")
print(" 2. To (收件人)")
print(" 3. Cc (抄送) - 支持 Cc 和 CC 两种格式")
print(" 4. Reply-To (回复地址)")
print(" 5. Date (日期) - 修改邮件列表显示的日期")
print(" 6. 全部修改")
print(" 0. 退出")
print("=" * 60)
modifications = {}
while True:
choice = input("\n请选择要修改的字段 (1-6, 0退出): ").strip()
if choice == '0':
break
elif choice == '1':
new_from = input("请输入新的 From 内容: ").strip()
if new_from:
modifications['From'] = new_from
print(f" From 将改为: {new_from}")
print(f" Sender 将同步修改为: {new_from}")
elif choice == '2':
new_to = input("请输入新的 To 内容: ").strip()
if new_to:
modifications['To'] = new_to
print(f" To 将改为: {new_to}")
elif choice == '3':
print("\n提示: Cc 字段可能是 'Cc:' 或 'CC:' 格式")
new_cc = input("请输入新的 Cc 内容 (留空删除): ").strip()
modifications['Cc'] = new_cc if new_cc else None
print(f" Cc 将改为: {new_cc if new_cc else '(删除)'}")
elif choice == '4':
new_reply = input("请输入新的 Reply-To 内容 (留空删除): ").strip()
modifications['Reply-To'] = new_reply if new_reply else None
print(f" Reply-To 将改为: {new_reply if new_reply else '(删除)'}")
elif choice == '5':
print("\n支持格式示例:")
print(" • 2024-01-01 09:00:00")
print(" • 2024-01-01")
print(" • 1 Jan 2024 09:00:00")
new_date = input("请输入新的日期时间: ").strip()
if new_date:
modifications['Date'] = new_date
print(f" Date 将改为: {new_date}")
elif choice == '6':
print("\n--- 修改 From (同时修改 Sender) ---")
new_from = input("新的 From (留空不变): ").strip()
if new_from:
modifications['From'] = new_from
print("\n--- 修改 To ---")
new_to = input("新的 To (留空不变): ").strip()
if new_to:
modifications['To'] = new_to
print("\n--- 修改 Cc (留空删除) ---")
print("提示: Cc 字段可能是 'Cc:' 或 'CC:' 格式")
new_cc = input("新的 Cc (留空删除): ").strip()
modifications['Cc'] = new_cc if new_cc else None
print("\n--- 修改 Reply-To (留空删除) ---")
new_reply = input("新的 Reply-To (留空删除): ").strip()
modifications['Reply-To'] = new_reply if new_reply else None
print("\n--- 修改 Date ---")
print("支持格式: 2024-01-01 09:00:00 或 1 Jan 2024 09:00:00")
new_date = input("新的 Date (留空不变): ").strip()
if new_date:
modifications['Date'] = new_date
break
else:
print("无效选项,请重新输入")
continue
more = input("\n是否继续修改其他字段? (y/n): ").strip().lower()
if more != 'y':
break
if modifications:
print("\n" + "=" * 60)
print("准备修改以下字段:")
for k, v in modifications.items():
if v is None:
print(f" {k}: (删除)")
else:
display_v = v[:80] + "..." if len(v) > 80 else v
print(f" {k}: {display_v}")
if 'From' in modifications:
print(f" Sender: (同步修改为 {modifications['From']})")
print("=" * 60)
confirm = input("\n确认修改? (y/n): ").strip().lower()
if confirm == 'y':
modify_email_header(eml_file, modifications)
print("\n修改完成!")
print("提示: 如需恢复,删除原文件,将 .bak 文件重命名为 .eml")
else:
print("已取消")
else:
print("未选择任何修改")
def main():
print("=" * 60)
print("邮件头信息修改工具")
print("支持: 单文件修改 + 批量修改(日期、发件人、收件人、抄送人、回复地址)")
print("修复: 修改发件人时自动同步修改 Sender 字段")
print("=" * 60)
print("\n请选择模式:")
print(" 1. 单文件修改(修改发件人、收件人、日期等)")
print(" 2. 批量修改日期(文件夹内所有邮件改成同一个日期)")
print(" 3. 批量修改发件人(文件夹内所有邮件改成同一个发件人,同时修改 Sender)")
print(" 4. 批量修改收件人(文件夹内所有邮件改成同一个收件人)")
print(" 5. 批量修改抄送人(文件夹内所有邮件改成同一个抄送人)")
print(" 6. 批量修改回复地址(文件夹内所有邮件改成同一个回复地址)")
print(" 0. 退出")
choice = input("\n请输入 (0-6): ").strip()
if choice == '0':
return
elif choice == '1':
path = input("\n请输入 .eml 文件路径: ").strip().strip('"')
modify_single_file(Path(path))
elif choice == '2':
folder = input("\n请输入文件夹路径: ").strip().strip('"')
date_str = input("请输入目标日期 (如 2024-01-01 09:00:00): ").strip()
if folder and date_str:
batch_modify_uniform(folder, 'Date', date_str, recursive=True)
else:
print("文件夹路径或日期不能为空")
elif choice == '3':
folder = input("\n请输入文件夹路径: ").strip().strip('"')
new_from = input("请输入新的发件人内容 (如 \"公司名\" <info@company.com>): ").strip()
if folder and new_from:
batch_modify_uniform(folder, 'From', new_from, recursive=True)
else:
print("文件夹路径或新发件人不能为空")
elif choice == '4':
folder = input("\n请输入文件夹路径: ").strip().strip('"')
new_to = input("请输入新的收件人内容 (如 \"客户\" <customer@email.com>): ").strip()
if folder and new_to:
batch_modify_uniform(folder, 'To', new_to, recursive=True)
else:
print("文件夹路径或新收件人不能为空")
elif choice == '5':
folder = input("\n请输入文件夹路径: ").strip().strip('"')
new_cc = input("请输入新的抄送人内容 (如 \"同事\" <colleague@company.com>,留空删除): ").strip()
if folder:
batch_modify_uniform(folder, 'Cc', new_cc if new_cc else None, recursive=True)
else:
print("文件夹路径不能为空")
elif choice == '6':
folder = input("\n请输入文件夹路径: ").strip().strip('"')
new_reply = input("请输入新的回复地址内容 (如 \"客服\" <service@company.com>,留空删除): ").strip()
if folder:
batch_modify_uniform(folder, 'Reply-To', new_reply if new_reply else None, recursive=True)
else:
print("文件夹路径不能为空")
else:
print("无效选项")
if __name__ == "__main__":
main()
7. 常见问题与解决方案
7.1 修改发件人后显示不正确
问题 :修改 From 后,邮件客户端仍显示长字符串。
原因 :邮件存在 Sender 字段,客户端优先显示 Sender。
解决 :修改发件人时同时修改 Sender 字段。
7.2 正则表达式匹配错误
问题 :修改 From 时错误修改了 Sender。
原因 :^From.*$ 会匹配以 "From" 开头的所有行。
解决 :使用 ^From: 精确匹配,确保字段名后紧跟冒号。
7.3 多行字段无法修改
问题 :抄送人字段跨多行时无法匹配。
原因 :单行正则表达式无法匹配多行。
解决 :使用 (?:.*\n(?:[ \t].*\n)*) 匹配续行。
7.4 打包后运行报错
问题 :exe 运行时提示缺少模块。
原因 :PyInstaller 未打包所有依赖。
解决 :使用 --hidden-import 参数指定隐藏依赖。
7.5 杀毒软件误报
问题 :打包的 exe 被杀毒软件报毒。
原因 :PyInstaller 打包的文件可能被误判。
解决:添加到白名单,或使用公司内部签名。
附录
A. 邮件头字段格式参考
| 字段 | 格式示例 |
|---|---|
| From | "张三" <zhangsan@example.com> |
| To | "李四" <lisi@example.com>, "王五" <wangwu@example.com> |
| Cc | "部门" <dept@example.com> |
| Date | Mon, 1 Jan 2024 09:00:00 +0800 |
| Sender | system@example.com |
B. 日期输入格式支持
- 2024-01-01 09:00:00
- 2024-01-01 09:00
- 2024-01-01
- 1 Jan 2024 09:00:00
- 1 Jan 2024 09:00
- 1 Jan 2024
C. 版本历史
| 版本 | 日期 | 更新内容 |
|---|---|---|
| v1.0 | 2026-03-29 | 单文件修改功能 |
| v1.1 | 2026-03-30 | 增加批量修改日期功能 |
| v1.2 | 2026-03-30 | 增加批量修改发件人/收件人 |
| v2.0 | 2026-03-30 | 修复 Sender 同步问题,打包成 exe |
文档结束