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

Python操作Outlook提取邮件附件时每次仅处理一半邮件的问题排查

问题背景

我在Unix上部署了定时任务,该任务每晚都会发送数百封邮件,我每天早上需要手动保存这些邮件的附件。为此我写了Python代码自动化处理,但每次运行脚本后,目标文件夹里只有一半左右的邮件附件被成功保存,剩余邮件未被处理。

运行日志
2021-11-18 06:13:30,688 : INFO : utils.util : Before clean up. 335 items in total
2021-11-18 06:13:42,098 : INFO : utils.util : After clean up. 167 items remained
2021-11-18 06:14:17,968 : INFO : utils.util : Before clean up. 167 items in total
2021-11-18 06:14:25,660 : INFO : utils.util : After clean up. 83 items remained
2021-11-18 06:14:34,762 : INFO : utils.util : Before clean up. 83 items in total
2021-11-18 06:14:38,591 : INFO : utils.util : After clean up. 41 items remained
2021-11-18 06:14:47,633 : INFO : utils.util : Before clean up. 41 items in total
2021-11-18 06:14:49,745 : INFO : utils.util : After clean up. 20 items remained
2021-11-18 06:14:56,348 : INFO : utils.util : Before clean up. 20 items in total
2021-11-18 06:14:57,426 : INFO : utils.util : After clean up. 9 items remained
2021-11-18 06:15:15,807 : INFO : utils.util : Before clean up. 9 items in total
2021-11-18 06:15:16,260 : INFO : utils.util : After clean up. 4 items remained
2021-11-18 06:15:22,981 : INFO : utils.util : Before clean up. 4 items in total
2021-11-18 06:15:23,215 : INFO : utils.util : After clean up. 1 items remained
2021-11-18 06:15:36,117 : INFO : utils.util : Before clean up. 1 items in total
2021-11-18 06:15:36,164 : INFO : utils.util : After clean up. 0 items remained

处理后的邮件数量变化为:335->167->83->41->20->9->4->1->0,符合每次减半的规律。

初始代码
import os
import win32com.client

def get_target_folder(folder: str):
    outlook = win32com.client.Dispatch('outlook.application')
    mapi = outlook.GetNamespace("MAPI")
    target = mapi
    dir = folder.split("\\")
    for d in dir:
        try:
            target = target.Folders(d)
        except:
            logger.error("Current folder path {}. The sub folder {} doesn't exist".format(target.FolderPath, d))
            target = None
            break

    return target


def save_job_status():
    mailfolder= config["OUTLOOK"]["JOBS"]
    keyword = config["OUTLOOK"]["KEYWORD"]
    destination = config["OUTLOOK"]["LOCALDATA"]
    criteria = f"@SQL=\"urn:schemas:httpmail:subject\" like '%{keyword}%'"

    folder = get_target_folder(folder=mailfolder)
    items = folder.items
    emails = items.restrict(criteria)
    logger.info("Before clean up. {} items in total".format(emails.count))

    for email in emails:
        try:
            attachments = email.attachments
            for attachment in attachments:
                filename = attachment.FileName
                attachment.SaveAsFile(os.path.join(destination, filename ))
            email.Delete()
        except:
            logger.error("Can't operate on the email {}".format(email.Subject))
    items = folder.items
    logger.info("After clean up. {} items remained".format(items.count))

额外说明:我提前设置了Outlook规则,所有符合条件的待处理邮件都会自动进入JOBS文件夹,代码中的criteria过滤条件是为了避免误操作额外添加的。

问题根源

核心问题是遍历集合的同时对集合本身执行删除操作。
当你用for email in emails遍历Outlook的Items集合时,迭代器会按顺序访问索引0、1、2...的元素:

  • 当你删除索引0的元素后,原索引1的元素会自动前移成为新的索引0,原索引2的元素成为新的索引1,以此类推
  • 下一次迭代时,迭代器会访问索引1的元素,等于直接跳过了原本的索引1(现在的索引0)的邮件,所以每处理1封就会跳过1封,最终表现为每次处理后剩余一半左右的邮件
解决方案

将正序遍历集合改为倒序索引访问,从最大索引值向最小索引值遍历,删除末尾元素不会影响前面元素的索引值,修改后的核心代码如下:

count = emails.count
for i in range(count-1, -1, -1):
    print("{} {}".format(i, emails[i].subject))
    # 保存附件逻辑不变
    attachments = emails[i].attachments
    for attachment in attachments:
        filename = attachment.FileName
        attachment.SaveAsFile(os.path.join(destination, filename ))
    emails[i].Delete()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 13:57:02