提高python中IMAP批量消息删除的速度

时间:2012-09-23 22:04:15

标签: python iteration imaplib

目标是使用imaplib删除大量电子邮件。电子邮件文件夹每月收到大约300,000条新邮件。只应删除超过1个月的邮件。如果执行此脚本,它将删除旧消息,但删除需要花费大量时间,而且迭代操作简单无效。这需要几个小时。 通过尝试通过多处理来提高速度会产生错误。

您有什么建议可以提高删除大量邮件的速度?

import sys
import datetime
from imaplib import IMAP4

# get the date a month from the current
monthbefore = (datetime.date.today() - datetime.timedelta(365/12)).strftime("%d-%b-%Y")

m = IMAP4('mail.domain.com')
m.login('user@domain.com', 'password')

# shows how many messages in selected folder
print m.select('Folder')
typ, data = m.select('Folder')

# find old messages
typ, data = m.search(None, '(BEFORE %s)' % (monthbefore))

# delete them
print "Will be removed:\t", data[0].split()[-1],"messages"
for num in data[0].split():
  m.store(num, '+FLAGS', '\\Deleted')
  sys.stderr.write('\rRemoving message:\t %s' % num)

# now expunge marked for deletion messages, close connection and exit
print "\nGet ready for expunge"
m.expunge()
print "Expunged! Quiting."
m.close()
m.logout()

更新:重新编写代码的一部分,这是工作变量的1000倍(我的服务器一次支持超过1000封邮件的存储命令):

    def chunks(l, n):
        # yields successive n-sized chunks from l.
        for i in xrange(0, len(l), n):
            yield l[i:i+n]

    mcount = data[0].split()[-1]
    print "Will be removed", mcount, "messages"
    for i in list(chunks(data[0].split(), 1000)):
        m.store(",".join(i), '+FLAGS', '\\Deleted')
        sys.stderr.write('\rdone {0:.2f}%'.format((int(i[-1])/int(mcount)*100)))

4 个答案:

答案 0 :(得分:5)

我认为这里的主要问题是你为每条消息调用STORE。这些往返服务器的每次往返都需要时间,当你进行大量删除时,这确实会增加。

要避免所有这些调用STORE尝试使用多个消息ID调用它。您可以传递单独列出的逗号(例如"1,2,3,4"),消息ID范围(例如"1:10")或两者的组合(例如"1,2,5,1:10")。请注意,大多数服务器似乎对每次调用允许的消息ID数量有限制,因此您可能仍需要将ID分块(例如200条消息)并多次调用STORE。这仍然比每个消息调用STORE快得多。

有关进一步参考,请参阅RFC 3501的STORE Command部分。它显示了一个采用一系列消息ID的STORE命令的示例。

答案 1 :(得分:1)

删除需要一定的时间,如果你一次只执行一次,则需要很长时间。与您等待服务器执行其操作所花费的时间相比,for循环的开销是微不足道的。几个小时不会脱节,也不会让我感到特别棘手;我确信你已经几个小时了。如果不这样做,请尽早开始。

尽管如此,如果这是一个问题,你仍然可以通过线程或多处理进入正确的轨道。我不知道你的意思是“给出错误”;在放弃这种方法之前,一点点的特异性可能会很好。如果您的意思是您的服务器不允许多个同时登录,则可以在您的IMAP服务器上配置。 (我使用CommuniGate Pro处理我的域的电子邮件,它允许这样做。)

另一种方法是每天运行一次删除脚本,甚至每小时运行一次,以便在一个月内分摊时间成本。您也可以尝试使用POP3而不是IMAP来查看此应用程序是否更快。

答案 2 :(得分:1)

我担心你无能为力。在IMAP中,将邮件标记为已删除非常快;这是杀手的expunge

你不能在多处理中这样做,因为只允许一个线程锁定邮箱进行物理删除。

如果您尝试运行多处理删除 - 在大多数服务器上我相信您实际上可以 - 您将大大加快已经非常快速的流程。但是运行expunge的单线程需要锁定很长时间;根据服务器的不同,您甚至可能无法在此“死时间”登录。其他一些服务器(我认为Icewarp的Merak)将允许在清除期间进行正常操作(在第一次完成之前,你根本不允许运行第二次清除)。

<强>更新

我做了一些实验。我发现要通过imaplib建立不同的连接,登录本身必须移到Thread

所以我设置了这样的应用程序:

  • 主应用程序登录并检索要删除的邮件列表
  • 将消息分为N个块
  • 启动运行登录的N个线程
  • 所有线程等待一小段时间让每个线程完成登录(以便所有连接的消息索引相同);我真的应该在这里使用同步
  • 每个线程开始删除其分配的消息部分,然后注销
  • 当所有主题完成后,主应用程序继续并清除邮箱

我注意到N = 2时性能提高,N = 3时增加。然后N = 4没有增加,即,一组四个中的每个线程花费相同的时间来删除25个消息,而不是用于删除33个消息的一组三个中的线程。 N再次没有从5增加到7.N = 8时性能开始下降;十点,服务器停止接受连接。

在我最好的情况下,我估计删除时间大约是标称值的40%,并且有三个线程运行;我不确定这是否证明了这一点。

但这些值很可能在很大程度上取决于服务器架构和硬件(有多少处理器和内核,多少内存)以及允许的最大并发连接数。因此,您可以从多线程方法中获得更多优势。

我还运行了一些测试服务器端。由于大多数IMAP服务器(http://en.wikipedia.org/wiki/Comparison_of_mail_servers)以Maildir格式的变体存储其数据,一个文件用于消息,并且消息的时间戳嵌入在文件名中,我试验了一个程序来删除任何包含较旧的时间戳。这种方法的缺点是要求用户注销,但速度非常快。

将文件标记为“要删除”(将“T”添加到信息文件后缀)也是可能的,并且我认为它不会真正干扰用户操作,所以剩下的就是发出一个清除命令使服务器物理杀死文件并立即重新计算配额(如果有)。

如果可以获得对服务器的访问权限,则定期运行此类程序将更有效地完成消息过期。

答案 3 :(得分:0)

投入一大块对我有用,电子邮件服务器为自己打破了它。根据您的需要为非Gmail IMAP服务器量身定制:

#!/bin/python

import datetime
import imaplib

m = imaplib.IMAP4_SSL("imap.gmail.com")  # server to connect to
m.login('gmail@your_gmail.com', 'your_password')

print m.select('[Gmail]/All Mail')  
before_date = (datetime.date.today() - datetime.timedelta(365)).strftime("%d-%b-%Y")  # date string, 04-Jan-2013
typ, data = m.search(None, '(BEFORE {0})'.format(before_date))  

if data != ['']:  # messages exist
    no_msgs = data[0].split()[-1]  # last msg id in the list
    print "To be removed:\t", no_msgs, "messages found with date before", before_date
    m.store("1:{0}".format(no_msgs), '+X-GM-LABELS', '\\Trash')  # move to trash, can also set Delete Flag here instead
    print "Deleted {0} messages. Closing connection & logging out.".format(no_msgs)
else:
    print "Nothing to remove."

#This block empties trash, Gmail auto purges trash after 30 days anyways.
print("Emptying Trash & Expunge...")
m.select('[Gmail]/Trash')  # select all trash
m.store("1:*", '+FLAGS', '\\Deleted')  #Flag all Trash as Deleted
m.expunge()  # not need if auto-expunge enabled in Gmail

m.close()
m.logout()