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

使用win32com批量保存Outlook附件报错:管理员限制同时打开项数

问题

我正在遍历从CSV加载到DataFrame中的entryId,通过win32com.client访问Outlook MAPI,将邮件附件保存到本地目录,同时把附件名称、路径和entryId存入新DataFrame用于后续分析。扫描248封邮件后,无论具体是哪封,都会触发错误,且确定错误仅出现在attachment.SaveAsFile(path)行——移除该行后程序可正常运行。我尝试过将对象设为None、调用outlook.Logoff()等方式释放引用,但问题依旧。

环境信息

  • Outlook版本:2202(Build 14931.20764)
  • Pywin32版本:227
  • Python版本:3.7.1

原始代码

import pandas as pd
import win32com.client

df = pd.DataFrame(columns=['attName', 'path', 'entryId'])
id = 1
for email in emailData.itertuples():
    outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
    message = outlook.GetItemFromID(email.entryId)
    if message:
        receivedDate = message.ReceivedTime
        if message.Attachments.Count > 0:
           for attachment in message.Attachments:
                if attachment.Type in {1,4,5}:
                    if not attachment.DisplayName.endswith('.png') and not attachment.DisplayName.endswith('.jpg') and not attachment.DisplayName.endswith('.gif'):
                        attName = str(attachment.DisplayName)
                        print('	 Attachment: %s' % attachment.DisplayName)
                        path = "some directory\%s\%s" % (receivedDate.year, attachment.DisplayName)
                        attachment.SaveAsFile(path) # 移除该行则错误消失
                        attachment = None
                        df.loc[id] = ([attName, str(path), email.entryId])
                        id += 1
            attachments = None
        message.Close(1)
        outlook.Logoff()
        outlook = None

错误信息

File "C:\Anaconda3\envs\myenv\lib\site-packages\win32com\client\__init__.py", line 474, in __getattr__
    return self._ApplyTypes_(*args)
  File "C:\Anaconda3\envs\myenv\lib\site-packages\win32com\client\__init__.py", line 467, in _ApplyTypes_
    self._oleobj_.InvokeTypes(dispid, 0, wFlags, retType, argTypes, *args),
pywintypes.com_error: (-2147352567, 'Exception occurred.', (4096, 'Microsoft Outlook', 'Your server administrator has limited the number of items you can open simultaneously. Try closing messages you have opened or removing attachments and images from unsent messages you are composing.', None, 0, -2147220731), None)

更新尝试

参考建议调整代码后,仍触发完全相同的错误,怀疑对象未正确释放:

import win32com.client

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
for email in emailData.itertuples():
    message = outlook.GetItemFromID(email.entryId)
    if message:
       attachments = []
       for attachment in list(message.Attachments):
           attachments.append(attachment)
       for attachment in attachments:
            attachType = int(attachment.Type)
            if attachType in {1,4,5}: 
                attName = str(attachment.DisplayName)
                if not attName.endswith('.png') and not attName.endswith('.jpg') and not attName.endswith('.gif'):
                    path = "somedir\%s" % (attName)
                    attachment.SaveAsFile(path) # 移除该行则错误消失
                    del attachment
                    del path
                del attName
            del attachType
        del attachments
    message.Close(1)
    del message

解决方法

这个错误是Outlook的服务器/客户端限制导致的——同时打开的项目数超出阈值,而Python中COM对象的自动回收机制不够及时。以下是针对性修复方案:

1. 避免重复创建Outlook实例

把Outlook实例的创建移到循环外,不要每次迭代都新建MAPI会话,减少资源占用:

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
# 循环放在这里

2. 强制释放COM对象并触发垃圾回收

Python的del和赋值None无法立刻释放COM对象,需要调用gc.collect()强制回收。处理完每封邮件和附件后,显式释放并触发回收:

import gc
# 处理完单个附件后
del attachment
gc.collect()

# 处理完单封邮件后
message.Close(1)
del message
gc.collect()

3. 优化附件处理逻辑

不要缓存附件列表,直接通过索引获取附件,处理完立即释放,避免持有过多引用:

if message.Attachments.Count > 0:
    for idx in range(message.Attachments.Count):
        # 通过索引获取附件,避免遍历产生的引用残留
        attachment = message.Attachments.Item(idx + 1)
        try:
            # 你的附件判断和保存逻辑
            if attachment.Type in {1,4,5}:
                attName = str(attachment.DisplayName)
                if not attName.endswith(('.png', '.jpg', '.gif')):
                    path = f"some directory\\{receivedDate.year}\\{attName}"
                    # 确保路径存在,避免保存失败导致对象锁定
                    os.makedirs(os.path.dirname(path), exist_ok=True)
                    attachment.SaveAsFile(path)
                    df.loc[id] = [attName, str(path), email.entryId]
                    id += 1
        finally:
            # 无论成功失败都释放附件对象
            del attachment
            gc.collect()

4. 增加短暂延迟

在每封邮件处理后增加100-200ms的延迟,避免频繁请求触发限制:

import time
# 处理完单封邮件后
time.sleep(0.1)

完整修复后的代码示例

import pandas as pd
import win32com.client
import gc
import os
import time

df = pd.DataFrame(columns=['attName', 'path', 'entryId'])
id = 1

# 只创建一次Outlook实例
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")

for email in emailData.itertuples():
    message = None
    try:
        message = outlook.GetItemFromID(email.entryId)
        if not message:
            continue
        
        receivedDate = message.ReceivedTime
        if message.Attachments.Count > 0:
            for idx in range(message.Attachments.Count):
                attachment = message.Attachments.Item(idx + 1)
                try:
                    if attachment.Type in {1,4,5}:
                        attName = str(attachment.DisplayName)
                        if not attName.endswith(('.png', '.jpg', '.gif')):
                            path = f"some directory\\{receivedDate.year}\\{attName}"
                            # 确保目录存在
                            os.makedirs(os.path.dirname(path), exist_ok=True)
                            attachment.SaveAsFile(path)
                            df.loc[id] = [attName, str(path), email.entryId]
                            id += 1
                            print(f"	 Saved attachment: {attName}")
                finally:
                    del attachment
                    gc.collect()
        
        # 关闭邮件
        message.Close(1)
        # 短暂延迟
        time.sleep(0.1)
    finally:
        if message:
            del message
        gc.collect()

# 最后释放Outlook实例
outlook.Logoff()
del outlook
gc.collect()

# 保存结果DataFrame
df.to_csv("attachment_records.csv", index=False)

内容的提问来源于stack exchange,提问作者bbpandagrl92

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 19:45:42