Gmailの添付ファイルをpythonで取得する
課題
データ連携のツールとしてmailのケースがたまにあります
それを予測データとして他のツールにimportしたりすると思いますが
- ローカルにダウンロード
- ダウンロードしたデータをDBにimport と人作業の部分が発生してしまうのでそれをどうにかしたい
その課題から、mailに添付されたファイルを自動で取得できるようした時のメモ
事前準備
今回Gmailアカウントを対象としてます Gmailは自前で作成したコードから接続を試みると"安全性の低いアプリ"と判断されてrejectするので まずは安全性の低いアプリの許可を有効にする必要がある
Googleの安全性の低いアプリのアクセスページに入りログインして許可にします
使用する時だけこれをONにしたいのですが、この有効化手順の自動はstackoverflowのここ見る限りでは難しそうです
今回実行したpythonのバージョンは3.9.1となります
実行
必要なモジュール
import imaplib import base64 import os import email import datetime as dt
- 全て標準ライブラリになります。pipなどでの追加インストール作業は不要です
- imaplibはemailプロトコルのimap standardのpackage
- base64はASCIIへの変換、逆変換するものとしてimportしてます
次にmailのプロトコルを使った設定
mail = imaplib.IMAP4_SSL('imap.gmail.com', 993)
ログイン
email_user = 'email-address@gmail.com' email_pass = 'email-password' mail.login(email_user, email_pass)
エラーが起きなければ接続ができています Inbox内のメールから対象ファイルを取得したいので場所を指定します
mail.select('Inbox')
Inbox内の対象ファイルを検索します
今回は送信元のアドレス、期間で絞り込みます
t_addr = '送信元メールアドレス' t_date = '2021-07-01' t_date_format = dt.datetime.strptime(t_date, '%Y-%m-%d') search_option = f'(FROM "{t_addr}" SENTSINCE "{t_date_format.strftime("%d-%b-%Y")}")' type, data = mail.search(None, search_option)
t_date, t_date_formatは対象となる日付を指定し、それを日付型にしてます
また、searchが認識する日付型に再度変換してます('2021-07-01' -> '01-Jul-2021')
RFC-822に沿った日付型だとエラー。年月日までの型じゃないとダメみたいです
searchの最初の引数はcharsetとなり、特に指定する文字形式がなければNoneにします
次の引数でフィルタをかけます。最低1つの条件が必要となってます
imaplib.IMAP4.search
受け取ったtype, dataはこんな感じ
OK [b'477 1149 1522 1724 1954']
typeは成功したかどうか、成功した場合dataにはlist型でメッセージ番号がスペース区切りで入っています
これを直近のメッセージだけ取得する場合は下記のように書きます
t_number = data[0].split()[-1] # b'1954'
で、今後は受け取った番号のメッセージ内容を取得するためfetchを使います
type, data = mail.fetch(t_number, '(RFC822)')
受け取ったdataはlist型内にtupleとしてメッセージのコンテンツが入っています
またデータはbyte型なのでここでemailモジュールを使ってパースします
email_message = email.message_from_bytes(data[0][1])
パースしたメッセージを順に読み込んでいき、添付されたfileを検索
取得したらそれをローカルファイルに書き込みます
for part in email_message.walk(): file_name = part.get_filename() if not file_name: continue fns = file_name.split('?') output_file_name = base64.b64decode(fns[-2]).decode(fns[1]) with open(f'{os.getcwd()}/{output_file_name}', 'wb') as f: f.write(part.get_payload(decode=True))
get_filename()で添付ファイル名を取得 file_nameは場合によってこんな感じになっていると思います
file_name: =?ISO-2022-JP?B?nanikashiranomojiretsu=?=
これは下記の形式になっており
=?文字セット?エンコード方式?エンコード文字列?=
文字セット、エンコード方式から文字列をデコードする必要があるんですね
?で区切ったリストから文字セットとエンコード文字列(ファイル名)を取得したあと人が読める文字にデコード
これをアウトプットのファイル名(添付ファイルと同名)にし、payloadからファイルコンテンツを取得して書き込んでます
※エンコードされていないファイルの場合はfile_name
をそのまま使用します
いかがでしたでしょうか?メールのプロトコルって歴史があるためか、今のapiとかと比べると取得が大変なんですねー
ちなみに今回は受信メールを対象にしてますが、送信になるとまたプロトコルが変わりますよー
参照
IMAP4 プロトコルクライアント
Gmailで「安全性の低いアプリ」がブロックされた場合の対処方法
higashi kunimitsu