3

目標は、imaplibを使用して大量の電子メールメッセージを削除することです。電子メールフォルダは、月に約30万件の新しいメッセージを受信します。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を使用してSTOREを呼び出そうとします。リストされたコンマ区切り(eg "1,2,3,4")、メッセージIDの範囲(eg "1:10")、または両方の組み合わせ(eg)のいずれかを渡すことができます"1,2,5,1:10"。ほとんどのサーバーでは、呼び出しごとに許可されるメッセージIDの数に制限があるように思われるため、IDをブロック(たとえば200メッセージ)にチャンクして、STOREを複数回呼び出す必要があることに注意してください。これは、メッセージごとにSTOREを呼び出すよりもはるかに高速です。

詳細については、RFC 3501のSTOREコマンドのセクションを参照してください。これは、メッセージIDの範囲を取得するSTOREコマンドの例を示しています。

于 2012-10-11T07:39:52.960 に答える
1

削除にはある程度の時間がかかりますが、一度に1つずつ削除すると、かなり時間がかかります。ループのオーバーヘッドはfor、サーバーが処理を実行するのを待つために費やしている時間と比較してごくわずかです。数時間はラインから外れていませんし、特に問題があると私を襲うこともありません。数時間あると思います。そうでない場合は、すぐに開始してください。

それでも、それが問題である場合は、スレッド化またはマルチプロセッシングで正しい方向に進んでいます。「エラーを出す」とはどういう意味かわかりません。そのアプローチをあきらめる前に、もう少し具体性が良いかもしれません。サーバーが複数の同時ログインを許可しないことを意味する場合は、おそらくIMAPサーバーで構成できます。(私はCommuniGate Proを使用してドメインの電子メールを処理し、これを許可します。)

もう1つのアプローチは、削除スクリプトを1日1回、または1時間に1回実行して、時間コストが1か月に分散するようにすることです。また、IMAPの代わりにPOP3を試して、このアプリケーションの方が高速かどうかを確認することもできます。

于 2012-09-23T22:08:39.393 に答える
1

できることはほとんどないのではないかと思います。IMAPでは、メッセージに削除済みのフラグを付けるのは非常に簡単です。それexpungeがキラーです。

また、マルチプロセッシングではこれを行うことはできません。物理的な消去を行うためにメールボックスをロックできるのは1つのスレッドだけだからです。

マルチプロセッシング削除を実行しようとすると(ほとんどのサーバーで実際に実行できると思います) 、すでに非常に高速なプロセスを劇的に高速化できます。ただし、実行中のシングルスレッドexpungeは長時間ロックする必要があります。サーバーによっては、この「デッドタイム」の間にログインできない場合もあります。他のいくつかのサーバー(IcewarpのMerakだと思います)は、消去中に通常の操作を許可します(最初の消去が完了するまで、2番目の消去を実行することはできません)。

アップデート

私は少し実験をしました。を介して異なる接続を確立するimaplibには、ログイン自体をに移動する必要があることがわかりましたThread

だから私はこのようにアプリを設定しました:

  • メインアプリがログインし、削除するメッセージのリストを取得します
  • メッセージをN個のチャンクに分割します
  • ログインを実行するN個のスレッドを開始します
  • すべてのスレッドは、すべてのスレッドがログインを完了するまで少し待ちます(メッセージインデックスがすべての接続で同じになるように)。私は本当にここで同期を採用すべきでした
  • 各スレッドは、メッセージの割り当てられた部分の削除を開始し、ログアウトします
  • すべてのスレッドが終了すると、メインアプリは続行し、メールボックスを削除します

N = 3で増加した、N=2でのパフォーマンスの向上に気づきました。次に、N = 4の増加はありません。つまり、4つのセットの各スレッドが25のメッセージを削除するのに、33のメッセージを削除するために使用される3つのセットのスレッドよりも同じ時間がかかりました。ここでも、Nが5から7に増加することはありません。N= 8で、パフォーマンスが低下し始めました。10時に、サーバーは接続の受け入れを停止しました。

私の最良のシナリオでは、3つのスレッドが実行されている場合、削除時間は公称値の約40%であると見積もっています。これが問題を正当化するかどうかはわかりません。

ただし、これらの値は、サーバーアーキテクチャ、ハードウェア(プロセッサとコアの数、メモリの数)、および許可される同時接続の最大数に大きく依存する可能性があります。したがって、マルチスレッドアプローチをさらに活用できる可能性があります。

また、サーバー側でいくつかのテストを実行しました。ほとんどのIMAPサーバー(http://en.wikipedia.org/wiki/Comparison_of_mail_servers)は、データをMaildir形式のバリアント、メッセージ用に1つのファイルで保存し、メッセージのタイムスタンプがファイル名に埋め込まれているため、実験しました。古いタイムスタンプを含むファイルを削除するプログラム。この方法には、ユーザーのログオフが必要になるという欠点がありますが、非常に高速です。

また、ファイルを「削除する」(情報ファイルのサフィックスに「T」を追加)としてマークすることも可能であり、ユーザーの操作に実際に干渉することはないと思います。残りは、消去コマンドを発行して作成するだけです。サーバーはファイルを物理的に強制終了し、クォータがある場合はすぐに再計算します。

このようなプログラムを定期的に実行すると、サーバーへのアクセスが取得できれば、メッセージの有効期限がより効率的に達成されます。

于 2012-09-23T22:16:41.460 に答える
0

それで1つの大きなチャンクを投げることは私のために働きます、電子メールサーバーはそれ自身のためにそれを分解します。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 に答える