outlookのメールボックスがゴミだらけになってるけど標準の検索やフィルタ機能で削除するのは結構痒いところに手が届かなかったりするのでPython利用で削除してみた。
まず、outlookを制御する場合、Win32 COMを経由して制御することになり、Windows版Pythonインストール時に入れられるpywin32ライブラリでコレが出来る。
import win32com
pywin32にはWindowsの色々にアクセス出来るけど今回はCOMだけなのでコレで。
outlookにアクセスする基本形は
1 2 |
outlook = win32com.client.DispatchWithEvents("Outlook.Application", OutlookEventHandler) mapi = outlook.GetNamespace("MAPI") |
outlookがアプリケーションアクセス用で、ネットでさらったコードだとDispatchを使ってたけど、今回は処理をする時にイベント処理が必要なのでWithEventsを使ってる。 mapiはメールボックスとかの各種情報にアクセスするネームスペース。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
accounts = mapi.Folders # アカウント一覧を取得(Outlookのナビゲーショントップレベルのフォルダ一覧はアカウント毎なので) for account in accounts: if account.Name == 'オンライン アーカイブ - myaddress@mydomain.tld': # オンラインアーカイブだけ整理、全アカウント対象なら不要 folders = account.Folders # アカウントの中のフォルダは実際のフォルダ、削除済みとかメール以外の色々も紛れてくる for folder in folders: if folder.Name == '受信トレイ': # 受信トレイだけ整理、全部やりたければ不要 filter = "urn:schemas:httpmail:textdescription LIKE '%配信の解除:配信登録・解除画面はこちら%'" # 検索条件、本分に消したいメルマガの定型文を入れている、フィルタ書式系はMSのVBAとかの情報でそのまま通る search = outlook.AdvancedSearch("'%s'"%folder.FolderPath, filter, False, "search and destroy") # outlookのAdvancedSearchを呼び出して処理する、処理対象をfolder.FolderPathで引っ張ってきて、その次がさっきのフィルタ、第三引数はサブフォルダ処理するかで今回はFalse、第四引数はタグで適当 while complete == False: # 検索が終わるまで待機する処理、complete自体は後で time.sleep(1) if search.Results.Count > 0: # 検索結果があれば処理開始 item = search.Results.GetFirst() # 最初の検索結果を取得する while item: print('Delete:', item.Subject) # 削除するメールのサブジェクトを表示 item.Delete() # 実際に削除する(Outlookの削除済みフォルダに移動される) item = search.Results.GetNext() # 次の検索結果を取得する、最後だとNoneが帰るのでwhileを抜ける |
この辺までは大体既存の情報検索とMSのドキュメントですんなり行ったんだけど、completeの所がちょいと手間取った。 AdvancedSearchしてすぐにsearchにアクセスしても、その先はoutlookなので実態じゃなくてoutlookのインスタンスで検索が終わってないと0件だったり、件数があっても処理してる間にどんどん増えていったりするので、検索が完了するまで待機する必要がある(AdvancedSearchは結果を返してるわけじゃなく、AdvancedSearchを開始してソコへの繋ぎを即座に返している)
で、MSのVBA Learnとかを見るとAdvancedSearchが完了するとAdvancedSearchCompleteイベントが発生するよ、って言うのはあったけど、その時のハンドラをPythonからアクセスするのがイマイチ良くわからず、VBAサンプルだとApplication_AdvancedSearchCompleteとか書いてたけど、どうやってアクセスするんだろと。 で、色々なCOMアクセスのサンプルとかを漁っていった結果、多分こうじゃないかな?と色々な名称を書いていった結果・・・
1 2 3 4 |
class OutlookEventHandler(object): def OnAdvancedSearchComplete(self, event): global complete complete = True |
コレが正解だった、global使ってるのは格好悪いけどハンドラインスタンスにアクセスする方法が良くわからずだったのでコレで妥協。 importの下でcomplete = Falseでcompleteを定義してグローバルアクセスしてる。
ネーミングルールがわかればまぁ後は簡単ですね、Onイベント名と言うメソッドを持ったハンドラー用クラスを作ってWithEventsの第二引数に入れてやればOKと言う簡単な答えだった。 でも、ネット探してもAdvancedSearchComplete使ってるコードがなかったんで案外手間取った、そんなわけでここにナレッジとして記載。
なお、大抵連続処理してるとOLEエラーとか色々な例外が出て途中で止まるので叩く部分は例外処理してリトライとか付けないと放置で大量削除とかは出来ない。
(66)