如何用Python从文件夹及EML文件生成PST文件?
从EML文件生成PST文件的解决方案需求
我有一个Python脚本,通过IMAP连接邮箱下载所有邮件为EML格式,并按对应目录存储。现在想把这些EML文件转换成PST文件,尝试用Win32操控Outlook但失败了,求可行的库或方法。
邮件下载代码
import imaplib import email from email.header import decode_header import os import re # Configuración de la cuenta de correo email_address = 'email@mail.com' password = 'pass' server = 'server.serveremail.com' # Conexión al servidor IMAP de Rackspace mail = imaplib.IMAP4_SSL(server) # Inicio de sesión mail.login(email_address, password) # Obtiene una lista de todas las carpetas status, folders = mail.list() # Carpeta para almacenar los archivos de respaldo backup_root_folder = 'backup_emails' backup_folder = '' os.makedirs(backup_root_folder, exist_ok=True) # Recorre todas las carpetas y respalda los mensajes de cada una for folder_info in folders: print(folder_info) # Decodifica la información de la carpeta folder_info_decoded = str(folder_info) folder_name_regex = re.compile(r'\"\.\" \"?INBOX(\.([^\"]+))?(\"|\')?') # Extrae el nombre de la carpeta usando expresiones regulares match = folder_name_regex.search(folder_info_decoded) if match: folder_name = match.group(2) if match.group(2) else "INBOX" # Verifica si el nombre de la carpeta contiene caracteres especiales if re.search(r'[<>:\"/\\|?*]', folder_name): print(f"La carpeta '{folder_name}' contiene caracteres especiales y será omitida.") continue print("************* SIPASO***** " + folder_name) # Crea la carpeta de respaldo correspondiente if folder_name != "INBOX": folder_name = folder_name.replace(".", os.path.sep) folder_name = folder_name.replace("'", "") backup_folder = os.path.join(backup_root_folder, folder_name) print(backup_folder) os.makedirs(backup_folder, exist_ok=True) # Selecciona la carpeta actual try: status, _ = mail.select(folder_name) if status != 'OK': print(f"No se pudo seleccionar la carpeta {folder_name}. Estado: {status}") continue else: print(status+" si seleccionamos----------------****>>>"+folder_name) except Exception as e: print(f"Error al seleccionar la carpeta {folder_name}: {e}") continue # Busca todos los mensajes en la carpeta actual try: status, messages = mail.uid('search', None, 'ALL') if status != 'OK': print(f"No se pudo buscar mensajes en la carpeta {folder_name}. Estado: {status}") continue else: print('si se obtuvieron los mensajes') except Exception as e: print(f"Error al buscar mensajes en la carpeta {folder_name}: {e}") continue print('*********************************************************************************') # Descarga cada mensaje y guarda como archivos .eml en la carpeta de respaldo for msg_id in messages[0].split(): _, msg_data = mail.uid('fetch', msg_id, '(RFC822)') msg = email.message_from_bytes(msg_data[0][1]) # Decodifica el asunto del mensaje para usarlo como nombre de archivo if msg.get('Subject'): subject, encoding = decode_header(msg.get('Subject'))[0] if isinstance(subject, bytes): subject = subject.decode(encoding or 'utf-8') else: subject = 'Sin asunto' # Reemplaza los caracteres no permitidos en el nombre del archivo cleaned_subject = re.sub(r'[<>:\"/\\|?*]', '', subject) # Elimina caracteres específicos no permitidos invalid_chars = ['', '\uf0fc', '\t'] for char in invalid_chars: cleaned_subject = cleaned_subject.replace(char, '') backup_file_path = '' folder_name = folder_name.replace(".", os.path.sep) folder_name = folder_name.replace("'", "") # Guarda el mensaje en un archivo .eml en la carpeta de respaldo if folder_name != "INBOX": backup_folder = os.path.join(backup_root_folder, folder_name) backup_file_path = os.path.join(backup_folder, f'{cleaned_subject}.eml') else: backup_file_path = os.path.join(backup_root_folder, f'{cleaned_subject}.eml') # Imprime el nombre de archivo antes de intentar guardarlo print("Guardando mensaje como:", backup_file_path) # Abre el archivo con el argumento 'wb' configurado para manejar todos los caracteres with open(backup_file_path, 'wb') as backup_file: backup_file.write(msg_data[0][1]) else: print(f"No se pudo obtener el nombre de la carpeta:") # Cierra la conexión mail.logout() print("Backup completado. Mensajes guardados en las carpetas de respaldo.")
生成PST文件的失败尝试代码
import os import win32com.client def create_pst_from_eml(source_folder, pst_file_path): Outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI") if not os.path.exists(pst_file_path): Outlook.AddStore(pst_file_path) try: PSTFolderObj = find_pst_folder(Outlook, pst_file_path) for root, _, files in os.walk(source_folder): for file_name in files: if file_name.endswith('.eml'): eml_file_path = os.path.join(root, file_name) with open(eml_file_path, 'rb') as eml_file: eml_data = eml_file.read() msg = BytesParser(policy=policy.default).parsebytes(eml_data) PSTFolderObj.Items.Add(msg) except Exception as exc: print(exc) finally: Outlook.RemoveStore(PSTFolderObj) def find_pst_folder(OutlookObj, pst_filepath): for Store in OutlookObj.Stores: if Store.IsDataFileStore and Store.FilePath == pst_filepath: return Store.GetRootFolder() return None def enumerate_folders(FolderObj): for ChildFolder in FolderObj.Folders: enumerate_folders(ChildFolder) iterate_messages(FolderObj) def iterate_messages(FolderObj): for item in FolderObj.Items: print("***************************************") print(item.SenderName) print(item.SenderEmailAddress) print(item.SentOn) print(item.To) print(item.CC) print(item.BCC) print(item.Subject) count_attachments = item.Attachments.Count if count_attachments > 0: for att in range(count_attachments): print(item.Attachments.Item(att + 1).Filename) if __name__ == "__main__": source_folder = r"C:\Users\\Desktop\respaldo\backup_emails" pst_file_path = r"C:\Users\\Desktop\respaldo\backup_emails.pst" create_pst_from_eml(source_folder, pst_file_path)
补充说明
第一个脚本可在Windows系统运行,成功下载EML格式邮件;第二个脚本尝试从已下载的EML文件生成PST文件,但过程中出现问题,恳请提供解决建议或方案。
可行解决方案
方法1:修复Win32com操控Outlook的代码
原代码的核心问题是:Outlook的Items.Add()无法直接接收Python的email.message.Message对象,且未同步创建PST内的文件夹结构。修复后的代码如下:
import os import win32com.client def create_pst_from_eml(source_folder, pst_file_path): Outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI") # 添加PST文件 if not os.path.exists(pst_file_path): Outlook.AddStore(pst_file_path) try: # 获取PST根文件夹 pst_root = find_pst_folder(Outlook, pst_file_path) if not pst_root: print("未找到目标PST文件") return # 遍历EML目录,同步创建PST文件夹并导入邮件 for root, dirs, files in os.walk(source_folder): # 计算当前目录相对于根目录的路径,创建对应PST文件夹 rel_path = os.path.relpath(root, source_folder) current_folder = pst_root if rel_path != ".": # 按路径分割创建多级文件夹 for folder_name in rel_path.split(os.sep): # 检查文件夹是否存在,不存在则创建 target_folder = None for f in current_folder.Folders: if f.Name == folder_name: target_folder = f break if not target_folder: target_folder = current_folder.Folders.Add(folder_name) current_folder = target_folder # 导入当前目录下的所有EML文件 for file_name in files: if file_name.endswith('.eml'): eml_path = os.path.join(root, file_name) try: # 使用Outlook原生方法读取EML msg = Outlook.OpenSharedItem(eml_path) # 复制到目标文件夹 msg.Move(current_folder) print(f"成功导入: {eml_path}") except Exception as e: print(f"导入失败 {eml_path}: {str(e)}") except Exception as exc: print(f"整体错误: {str(exc)}") finally: # 移除PST存储(若需保持Outlook中显示则注释此行) # Outlook.RemoveStore(pst_root) print("操作完成") def find_pst_folder(OutlookObj, pst_filepath): for Store in OutlookObj.Stores: if Store.IsDataFileStore and Store.FilePath == pst_filepath: return Store.GetRootFolder() return None if __name__ == "__main__": source_folder = r"C:\Users\你的用户名\Desktop\respaldo\backup_emails" pst_file_path = r"C:\Users\你的用户名\Desktop\respaldo\backup_emails.pst" create_pst_from_eml(source_folder, pst_file_path)
修复要点:
- 使用
Outlook.OpenSharedItem()直接读取EML文件,避免手动解析的兼容性问题 - 同步创建PST内的子文件夹结构,与EML存储目录匹配
- 用
msg.Move()完成邮件导入,符合Outlook对象模型的要求
方法2:使用第三方库(无需依赖Outlook)
如果不想依赖Outlook客户端,可以使用pypff-python库直接操作PST文件,支持跨平台:
- 安装依赖:
pip install pypff-python
- 示例代码:
import os import pypff import email def create_pst_with_pypff(source_folder, pst_file_path): # 创建新的PST文件 pst = pypff.file() pst.create(pst_file_path) # 获取PST根文件夹 root_folder = pst.get_root_folder() # 遍历EML目录 for root, dirs, files in os.walk(source_folder): rel_path = os.path.relpath(root, source_folder) current_folder = root_folder # 创建对应子文件夹 if rel_path != ".": for folder_name in rel_path.split(os.sep): # 检查是否存在该文件夹 folder_exists = False for f in current_folder.sub_folders: if f.name == folder_name: current_folder = f folder_exists = True break if not folder_exists: current_folder = current_folder.add_sub_folder(folder_name) # 导入EML文件 for file_name in files: if file_name.endswith('.eml'): eml_path = os.path.join(root, file_name) try: with open(eml_path, 'rb') as f: msg = email.message_from_bytes(f.read()) # 创建PST中的邮件项 pst_msg = current_folder.add_sub_message() # 设置邮件基本属性 pst_msg.subject = msg.get('Subject', '无主题') pst_msg.sender_name = msg.get('From', '') pst_msg.delivery_time = msg.get('Date', '') pst_msg.recipients = msg.get('To', '') pst_msg.cc_recipients = msg.get('Cc', '') pst_msg.bcc_recipients = msg.get('Bcc', '') # 设置邮件内容 if msg.is_multipart(): for part in msg.walk(): if part.get_content_type() == 'text/plain': pst_msg.plain_text_body = part.get_payload(decode=True).decode('utf-8', errors='ignore') elif part.get_content_type() == 'text/html': pst_msg.html_body = part.get_payload(decode=True).decode('utf-8', errors='ignore') else: pst_msg.plain_text_body = msg.get_payload(decode=True).decode('utf-8', errors='ignore') # 添加附件 for part in msg.walk(): if part.get_filename(): att = pst_msg.add_attachment() att.name = part.get_filename() att.data = part.get_payload(decode=True) print(f"成功导入: {eml_path}") except Exception as e: print(f"导入失败 {eml_path}: {str(e)}") # 关闭PST文件 pst.close() print("PST文件创建完成") if __name__ == "__main__": source_folder = r"C:\Users\你的用户名\Desktop\respaldo\backup_emails" pst_file_path = r"C:\Users\你的用户名\Desktop\respaldo\backup_emails.pst" create_pst_with_pypff(source_folder, pst_file_path)
说明:该方法无需Outlook,直接操作PST文件结构,但对复杂邮件(如加密、特殊格式)的处理需要额外适配。
方法3:PowerShell批量导入(Windows环境)
如果熟悉PowerShell,可结合Outlook COM对象快速完成导入:
$sourceFolder = "C:\Users\你的用户名\Desktop\respaldo\backup_emails" $pstPath = "C:\Users\你的用户名\Desktop\respaldo\backup_emails.pst" # 创建Outlook对象 $outlook = New-Object -ComObject Outlook.Application $mapi = $outlook.GetNamespace("MAPI") # 添加PST if (-not (Test-Path $pstPath)) { $mapi.AddStore($pstPath) } # 获取PST根文件夹 $pstRoot = $mapi.Stores | Where-Object { $_.FilePath -eq $pstPath } | Select-Object -ExpandProperty GetRootFolder # 遍历EML文件 Get-ChildItem -Path $sourceFolder -Recurse -Filter *.eml | ForEach-Object { $emlPath = $_.FullName $relPath = $_.DirectoryName.Substring($sourceFolder.Length).TrimStart('\') # 创建对应文件夹 $currentFolder = $pstRoot if ($relPath) { $folders = $relPath -split '\' foreach ($folderName in $folders) { $targetFolder = $currentFolder.Folders | Where-Object { $_.Name -eq $folderName } if (-not $targetFolder) { $targetFolder = $currentFolder.Folders.Add($folderName) } $currentFolder = $targetFolder } } # 导入EML try { $msg = $outlook.OpenSharedItem($emlPath) $msg.Move($currentFolder) Write-Host "导入成功: $emlPath" } catch { Write-Host "导入失败 $emlPath: $_" } } # 释放COM对象 [System.Runtime.Interopservices.Marshal]::ReleaseComObject($outlook) | Out-Null
内容的提问来源于stack exchange,提问作者Brayan Gonzalez
相关产品推荐
相关产品推荐

