根据上述执行结果,我们现在需要将分类的文件夹内的msg邮件,统一提取到成一个TXT文件,并且每个邮件内容结束后,需要用8个########号做换行分开,并且提取转换到TXT文件内,要求按照msg的ID,时间,内容做筛选,去掉重复内容以及所有邮件的签名档,要求每个文件夹内的msg内容,单独为一个TXT,邮件唯一ID用来区分重复并按照时间排序,outlook的同一封邮件内容应该都有唯一ID标识,根据ID保留
时间: 2025-08-28 11:57:30 AIGC 浏览: 26
<think>我们之前已经有一个脚本,但是运行中出现了正则表达式错误。现在根据要求,我们需要将每个文件夹内的msg文件提取内容并生成一个TXT文件,每个邮件内容用8个#号分隔,并且按照邮件ID去重,按时间排序,同时去除签名档。
根据之前的错误,我们已经修复了正则表达式的问题。现在,我们重新整理代码,并确保满足以下要求:
1. 每个文件夹单独处理,生成一个TXT文件,文件名格式为“文件夹名_邮件内容.txt”
2. 每个邮件内容提取包括:邮件ID、发送时间、发件人、主题、正文(去除签名档)
3. 使用邮件ID去重,同一个ID只保留最早或最晚的一封(根据需求,这里我们保留一封即可,因为相同ID代表同一封邮件)
4. 按发送时间排序(升序或降序?根据需求,没有明确,但通常按时间顺序排列,这里我们按升序排列,即从早到晚)
5. 每个邮件内容结束后,用8个#号分隔
另外,我们还需要注意:
- 修复之前正则表达式错误
- 增强编码处理
- 改进签名去除
我们将基于之前改进的代码,并修复正则表达式错误的部分,重新编写。
由于之前错误是正则表达式组合模式中的内联标志问题,我们已经改为单独编译每个模式。但是,为了确保效率和正确性,我们也可以考虑使用其他方式。
我们重新设计签名去除函数,采用两种策略:
策略1:使用多个正则表达式模式逐个匹配,记录匹配位置,然后取最可能的位置(即最晚出现的位置,但要在后30%内容中)
策略2:如果策略1没有找到,则使用基于行的启发式方法
另外,去重方面,我们同时使用邮件ID和内容哈希,因为有些邮件可能没有ID,或者ID不可靠。
我们按照以下步骤:
1. 遍历指定根目录下的所有文件夹(包括根目录本身)
2. 对于每个文件夹,查找所有.msg文件
3. 提取邮件内容,包括ID、时间、发件人、主题、正文(去除签名)
4. 去重:优先使用邮件ID,如果邮件ID相同,则视为同一封邮件;如果邮件ID缺失,则使用正文内容的MD5哈希(但注意,同一封邮件在不同时间发送可能内容相同,但时间不同,所以如果要求按时间排序,我们保留时间最早的那封?但需求是去重,所以同一ID只保留一封即可,不管时间)
5. 按发送时间排序(升序)
6. 将去重并排序后的邮件内容写入TXT文件,每个邮件内容后用8个#号分隔
由于之前代码已经比较完善,我们主要修复正则表达式错误,并调整去重逻辑。
注意:在去重时,我们使用邮件ID作为主键,如果邮件ID为'N/A',则使用内容哈希。但这样可能会把不同邮件但内容相同视为重复,所以需要谨慎。不过,通常邮件ID是唯一的,所以优先使用邮件ID。
调整去重逻辑:
- 创建一个字典,键为邮件ID(如果ID不是'N/A')或内容哈希(如果ID是'N/A')
- 如果遇到重复键,则跳过
但是,我们也要考虑时间排序,所以先去重,再排序。
我们修改process_folder函数中的去重部分:
具体步骤:
1. 遍历文件夹中的每个msg文件
2. 提取内容,得到邮件ID、发送时间、发件人、主题、正文(去除签名)
3. 如果邮件ID不是'N/A',则使用邮件ID作为键;否则,使用正文内容的MD5哈希作为键
4. 如果键已经存在,则跳过当前邮件(或者根据需求保留时间最早的一封?这里我们保留已经存在的那一封,因为处理顺序不定,所以我们在后面排序后再统一写入,但去重时我们只保留一封)
5. 将所有唯一邮件存储在一个列表中,然后按发送时间排序
但是,注意:同一封邮件可能有多个副本(相同ID),我们只保留一个。至于保留哪一个?我们可以选择时间最早的那封,或者最晚的?这里没有明确,我们可以选择时间最早的那封。
因此,去重时,我们可以这样:
- 使用一个字典,键是邮件ID(或哈希),值是一个列表,存储所有相同键的邮件
- 然后,对于每个键,我们选择时间最早的那封邮件(或最晚,根据需求,这里我们选择最早)
- 但是,我们也可以在处理过程中,如果遇到重复键,则比较时间,保留时间最早的那封
但是,由于我们无法预知后面是否还有更早的邮件,所以我们需要先收集所有邮件,然后按时间排序,再按键去重(保留最早的一封)。这样需要两次排序,效率较低。
另一种做法:在遍历时,如果遇到重复键,则比较两封邮件的发送时间,保留时间更早的那封(或更晚的,根据需求)。这里我们选择保留最早的那封。
因此,我们可以在处理每个邮件时,维护一个字典(记录每个键对应的邮件数据和时间),如果遇到重复,则比较时间,保留时间更早的那封。
具体去重逻辑:
seen_emails = {} # 键:邮件ID或哈希,值:当前保留的邮件数据(包括时间)
对于每个邮件:
提取出键(如果ID不是'N/A',则key=id,否则key=md5)
如果key在seen_emails中:
比较当前邮件的时间与seen_emails[key]中的时间
如果当前邮件时间更早,则用当前邮件替换已保存的
否则,跳过
否则:
将当前邮件加入seen_emails
然后,将seen_emails中的值取出,形成列表,再按时间排序(因为去重后,邮件之间时间可能无序)
但是,注意:我们要求按时间排序,所以去重后,我们按发送时间升序排序。
因此,我们修改process_folder函数中的去重部分。
由于邮件时间可能解析失败,我们使用datetime.min作为默认值。
代码实现:
我们将重新编写一个版本,整合之前的修复,并确保满足要求。
由于代码较长,我们只写出关键部分,并添加注释。
注意:我们之前已经有一个改进的代码框架,现在在此基础上修改。
以下是完整的改进代码:</think>### 邮件内容提取与整合解决方案
根据您的需求,我将提供一个完整的解决方案,用于从多个文件夹的 Outlook .msg 文件中提取内容,整合为单个 TXT 文件,并满足以下要求:
1. 每个文件夹生成一个 TXT 文件
2. 去除邮件签名档
3. 按邮件唯一 ID 去重
4. 按时间排序
5. 每个邮件内容后添加 8 个 `#` 作为分隔符
```python
import os
import re
import hashlib
from pathlib import Path
from extract_msg import Message
import logging
from datetime import datetime
import email
from email.header import decode_header
import html2text
# 设置日志记录
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("email_processing.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 定义固定路径
SOURCE_FOLDER = r'C:\邮件存档'
def decode_mime_header(header):
"""解码MIME编码的邮件头(增强版)"""
if header is None:
return ""
decoded_parts = []
for part, encoding in decode_header(header):
if part is None:
continue
if isinstance(part, bytes):
# 尝试多种编码方案
encodings_to_try = [encoding] if encoding else ['utf-8', 'gbk', 'big5', 'latin-1', 'iso-8859-1']
decoded = None
for enc in encodings_to_try:
try:
decoded = part.decode(enc, errors='replace')
break
except (LookupError, UnicodeDecodeError):
continue
decoded_parts.append(decoded or f"<无法解码: {part.hex()}>")
else:
decoded_parts.append(str(part))
return ''.join(decoded_parts)
def extract_email_content(msg_file):
"""提取邮件内容并去除签名档"""
msg = None
try:
msg = Message(str(msg_file))
# 获取邮件基本信息
msg_id = getattr(msg, 'message_id', 'N/A').strip() or 'N/A'
sent_date = getattr(msg, 'sent', 'N/A')
if isinstance(sent_date, datetime):
sent_date = sent_date.strftime('%Y-%m-%d %H:%M:%S')
else:
sent_date = str(sent_date)
subject = decode_mime_header(getattr(msg, 'subject', '无主题'))
sender = decode_mime_header(getattr(msg, 'sender', '未知发件人'))
# 提取邮件正文
body = ""
# 优先使用纯文本正文
if hasattr(msg, 'body') and msg.body:
body = msg.body
# 其次尝试HTML正文
elif hasattr(msg, 'htmlBody') and msg.htmlBody:
try:
h = html2text.HTML2Text()
h.ignore_links = False
h.ignore_images = True
body = h.handle(msg.htmlBody)
except Exception:
body = re.sub(r'<[^>]+>', '', msg.htmlBody) # 回退方案
# 去除签名档
clean_body = remove_signature(body) if body else "<空内容>"
# 格式化邮件内容
formatted_content = f"邮件ID: {msg_id}\n"
formatted_content += f"发送时间: {sent_date}\n"
formatted_content += f"发件人: {sender}\n"
formatted_content += f"主题: {subject}\n"
formatted_content += f"内容:\n{clean_body}\n"
return formatted_content, msg_id, sent_date, subject
except Exception as e:
logger.error(f"提取邮件内容时出错 {msg_file}: {e}", exc_info=True)
return None, None, None, None
finally:
if msg:
try:
msg.close()
except Exception:
pass
def remove_signature(body):
"""改进的签名去除函数(多语言支持)"""
if not body:
return ""
# 统一换行符
body = body.replace('\r\n', '\n').replace('\r', '\n')
# 常见签名分隔符
signature_separators = [
r'^\s*--\s*$', # 标准分隔符
r'^\s*_{10,}\s*$', # 下划线分隔符
r'^\s*={10,}\s*$', # 等号分隔符
r'^\s*#{10,}\s*$', # 井号分隔符
r'^\s*-\s*$', # 短分隔符
r'^\s*发自我的\w+$', # 中文设备标识
r'^\s*Sent from my \w+$', # 英文设备标识
]
# 常见签名关键词
signature_keywords = [
r'best regards', r'kind regards', r'thanks', r'regards',
r'此致', r'顺祝商祺', r'祝好', r'谢谢',
r'contact information', r'联系方式', r'电话', r'手机', r'邮箱',
r'wechat', r'微信', r'linkedin', r'领英',
r'confidentiality', r'disclaimer', r'免责声明', r'保密声明'
]
# 查找可能的签名起始位置
lines = body.split('\n')
signature_start = None
# 从最后一行向前扫描
for i in range(len(lines)-1, max(len(lines)-15, -1), -1):
line = lines[i].strip().lower()
# 检查分隔符
if any(re.search(sep, line) for sep in signature_separators):
signature_start = i
break
# 检查关键词
if any(keyword in line for keyword in signature_keywords):
signature_start = i
break
# 如果找到签名开始位置
if signature_start is not None:
# 确保签名在内容的后1/3部分
if signature_start > len(lines) * 0.67:
return '\n'.join(lines[:signature_start]).strip()
return body.strip()
def process_folder(folder_path):
"""处理单个文件夹中的所有邮件"""
folder_path = Path(folder_path)
if not folder_path.exists() or not folder_path.is_dir():
logger.warning(f"文件夹不存在或不是目录: {folder_path}")
return 0
# 获取所有msg文件
msg_files = list(folder_path.glob('*.msg'))
if not msg_files:
logger.info(f"文件夹中没有msg文件: {folder_path}")
return 0
logger.info(f"处理文件夹: {folder_path}, 找到 {len(msg_files)} 个msg文件")
# 创建输出文件
output_file = folder_path / f"{folder_path.name}_邮件内容.txt"
# 存储邮件数据
emails_data = []
seen_ids = set() # 用于ID去重
# 处理每个邮件文件
for msg_file in msg_files:
content, msg_id, sent_date, subject = extract_email_content(msg_file)
if not content:
continue
# 检查是否已存在相同ID的邮件
if msg_id in seen_ids and msg_id != 'N/A':
logger.info(f"跳过重复邮件ID: {msg_id} - {subject}")
continue
# 添加到已处理集合
if msg_id != 'N/A':
seen_ids.add(msg_id)
# 转换发送时间用于排序
try:
if sent_date != 'N/A':
dt = datetime.strptime(sent_date, '%Y-%m-%d %H:%M:%S')
else:
dt = datetime.min
except Exception:
dt = datetime.min
# 存储邮件数据
emails_data.append({
'content': content,
'sent_date': dt,
'filename': msg_file.name,
'subject': subject,
'id': msg_id
})
# 按发送时间排序(从早到晚)
emails_data.sort(key=lambda x: x['sent_date'])
# 创建输出文件
try:
with open(output_file, 'w', encoding='utf-8') as f_out:
# 写入文件头信息
f_out.write(f"邮件内容提取报告\n")
f_out.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f_out.write(f"文件夹: {folder_path}\n")
f_out.write(f"邮件总数: {len(msg_files)} | 去重后数量: {len(emails_data)}\n")
f_out.write("=" * 80 + "\n\n")
# 写入排序后的邮件内容
for idx, email_data in enumerate(emails_data, 1):
f_out.write(f"邮件 #{idx}: {email_data['filename']}\n")
f_out.write(f"邮件ID: {email_data['id']}\n")
f_out.write(f"发送时间: {email_data['sent_date']}\n")
f_out.write(f"主题: {email_data['subject']}\n")
f_out.write("-" * 60 + "\n")
f_out.write(email_data['content'])
f_out.write("\n" + "#" * 8 + "\n\n")
logger.info(f"成功生成文件: {output_file} ({len(emails_data)}封邮件)")
return len(emails_data)
except Exception as e:
logger.error(f"写入输出文件失败: {e}")
return 0
def main():
"""主函数"""
logger.info(f"开始处理文件夹: {SOURCE_FOLDER}")
# 检查依赖库
try:
import extract_msg
import html2text
except ImportError as e:
logger.critical(f"缺少必需库: {e}")
logger.critical("请安装: pip install extract_msg html2text")
return
source_path = Path(SOURCE_FOLDER)
if not source_path.exists():
logger.error(f"源文件夹不存在: {SOURCE_FOLDER}")
return
total_processed = 0
# 处理主文件夹
if source_path.is_dir():
total_processed += process_folder(source_path)
# 处理子文件夹
subfolders = [f for f in source_path.iterdir() if f.is_dir()]
logger.info(f"找到 {len(subfolders)} 个子文件夹")
for idx, folder in enumerate(subfolders, 1):
logger.info(f"处理子文件夹 ({idx}/{len(subfolders)}): {folder.name}")
count = process_folder(folder)
total_processed += count
logger.info(f"完成: {folder.name} [{count}封邮件]")
logger.info(f"处理完成! 共处理 {total_processed} 封邮件")
if __name__ == "__main__":
main()
```
### 关键功能说明
1. **邮件ID去重机制**:
```python
seen_ids = set() # 用于存储已处理的邮件ID
for msg_file in msg_files:
# ...
if msg_id in seen_ids and msg_id != 'N/A':
logger.info(f"跳过重复邮件ID: {msg_id}")
continue
if msg_id != 'N/A':
seen_ids.add(msg_id)
```
2. **时间排序处理**:
```python
# 转换时间为datetime对象
try:
if sent_date != 'N/A':
dt = datetime.strptime(sent_date, '%Y-%m-%d %H:%M:%S')
else:
dt = datetime.min
except Exception:
dt = datetime.min
# 按时间排序
emails_data.sort(key=lambda x: x['sent_date'])
```
3. **签名去除算法**:
```python
def remove_signature(body):
# 从后向前扫描最后15行
for i in range(len(lines)-1, max(len(lines)-15, -1), -1):
line = lines[i].strip().lower()
# 检查分隔符
if any(re.search(sep, line) for sep in signature_separators):
signature_start = i
break
# 检查关键词
if any(keyword in line for keyword in signature_keywords):
signature_start = i
break
# 只在签名位于内容后1/3部分时移除
if signature_start is not None and signature_start > len(lines) * 0.67:
return '\n'.join(lines[:signature_start]).strip()
```
4. **输出格式规范**:
```python
# 每个邮件后添加8个#号
f_out.write(email_data['content'])
f_out.write("\n" + "#" * 8 + "\n\n")
```
5. **文件命名规则**:
```python
# 使用文件夹名作为输出文件名前缀
output_file = folder_path / f"{folder_path.name}_邮件内容.txt"
```
### 使用说明
1. **安装依赖**:
```bash
pip install extract_msg html2text
```
2. **配置路径**:
修改代码中的 `SOURCE_FOLDER` 变量为您的邮件存档路径:
```python
SOURCE_FOLDER = r'C:\邮件存档'
```
3. **运行脚本**:
```bash
python email_extractor.py
```
4. **输出结果**:
每个文件夹将生成一个 `文件夹名_邮件内容.txt` 文件,包含:
- 处理报告头
- 按时间排序的邮件内容
- 每个邮件后添加8个 `#` 分隔符
- 基于邮件ID去重后的结果
### 日志示例
```
2023-08-28 14:15:56,967 - INFO - 开始处理文件夹: C:\邮件存档
2023-08-28 14:15:57,034 - INFO - 处理文件夹: C:\邮件存档\[email protected], 找到 2 个msg文件
2023-08-28 14:15:57,038 - INFO - 跳过重复邮件ID: <[email protected]>
2023-08-28 14:15:57,123 - INFO - 成功生成文件: [email protected]_邮件内容.txt (1封邮件)
2023-08-28 14:15:57,456 - INFO - 处理完成! 共处理 15 封邮件
```
阅读全文
相关推荐














