You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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文件,支持跨平台:

  1. 安装依赖:
pip install pypff-python
  1. 示例代码:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 18:55:55