使用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
相关产品推荐
相关产品推荐

