邮件头信息修改工具开发技术文档

1. 项目背景与需求

1.1 业务场景

在日常邮件处理工作中,经常需要对导出的 .eml 邮件文件进行信息修改,包括:

  • 修改发件人(From)
  • 修改收件人(To)
  • 修改抄送人(Cc)
  • 修改回复地址(Reply-To)
  • 修改邮件日期(Date)

1.2 技术挑战

  • 邮件头格式复杂,存在多行字段
  • 部分邮件存在 Sender 字段,需要与 From 同步修改
  • 需要支持单文件和批量两种模式
  • 最终交付物需方便非技术人员使用

2. 环境准备

2.1 安装 Python

  1. 访问 Python 官网:https://www.python.org/downloads/
  2. 下载 Python 3.8 或更高版本
  3. 安装时务必勾选 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

文档结束

相关推荐
小碗羊肉2 小时前
【从零开始学Java | 第二十四篇】泛型的继承和通配符
java·开发语言·新手入门
源码之家2 小时前
计算机毕业设计:Python二手车交易价格预测分析平台 Django框架 随机森林 可视化 数据分析 汽车 车辆 大数据 hadoop(建议收藏)✅
大数据·爬虫·python·机器学习·django·汽车·课程设计
wefly20172 小时前
jsontop.cn使用全攻略:免费无广告的在线工具站,电脑手机通用
开发语言·安全·json·ecmascript·json在线转换
郝学胜-神的一滴2 小时前
图形学基础:OpenGL、图形引擎与IG的核心认知及核心模式解析
开发语言·c++·qt·程序人生·图形渲染
愤豆2 小时前
15-Java语言核心-并发编程-并发容器详解
java·开发语言
xiaoliuliu123452 小时前
R语言4.5.0安装教程:详细步骤+自定义安装路径(64位)
开发语言·r语言
清水白石0082 小时前
Python 性能优化避坑指南:回归风险防控、基准压测与安全回滚实战
python·性能优化·回归
小宇的天下2 小时前
Calibre LVS Circuit Comparison(3)
开发语言·php·lvs
96772 小时前
多线程编程:整个互斥的流程以及scoped_lock的用法,以及作用,以及 硬件上的原子操作和逻辑上的原子操作
开发语言·c++·算法