3

目标是使用 imaplib 删除一堆电子邮件。电子邮件文件夹每月接收大约 300k 条新消息。仅应删除超过 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

4 回答 4

5

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

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

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

于 2012-10-11T07:39:52.960 回答
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”),所以剩下的就是发出删除命令服务器物理地杀死文件并立即重新计算配额(如果有)。

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

于 2012-09-23T22:16:41.460 回答
1

删除需要一定的时间,如果一次删除一个,则需要很长时间。for与您等待服务器执行其操作所花费的时间相比,循环的开销是微不足道的。几个小时并不过分,我也不觉得特别有问题;你几个小时,我敢肯定。如果你不这样做,那就早点开始吧。

尽管如此,如果这是一个问题,那么您在线程或多处理方面处于正确的轨道上。我不知道您所说的“给出错误”是什么意思;在放弃这种方法之前,再具体一点可能会很好。如果您的意思是您的服务器不允许多个同时登录,则可以在您的 IMAP 服务器上进行配置。(我使用 CommuniGate Pro 为我的域处理电子邮件,它允许这样做。)

另一种方法是每天运行一次删除脚本,甚至每小时运行一次,这样时间成本​​就会分摊到一个月内。您也可以尝试使用 POP3 而不是 IMAP,看看它对于这个应用程序是否更快。

于 2012-09-23T22:08:39.393 回答
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()
于 2014-02-24T20:11:49.233 回答