【Python】imaplibでメールを受信・取得する:IMAP4_SSLとemailモジュールの連携

目次

概要

Python標準ライブラリの imaplib を使用して、IMAPサーバーからメールを受信・取得する方法を解説します。

単にデータを受信するだけでなく、email モジュールと連携して、件名や本文、送信元アドレスを扱いやすいオブジェクト(EmailMessage型)として解析するまでの手順を網羅します。

仕様(入出力)

  • 入力:
    • IMAPサーバー情報(ホスト、ポート)
    • アカウント情報(メールアドレス、パスワード)
    • 検索条件(件名、未読、全件など)
  • 出力:
    • メールオブジェクト(EmailMessage
    • 件名、本文、差出人などの抽出データ
  • 前提:
    • Python標準ライブラリ(imaplib, email)のみを使用。
    • Gmail等の場合、アプリパスワードの生成が必要な場合があります。

基本の使い方

IMAPサーバーに接続し、受信トレイ(INBOX)から最新のメールを1件取得して解析する基本的なフローです。

import imaplib
from email import message_from_bytes, policy

# 1. サーバー接続とログイン
imap = imaplib.IMAP4_SSL("imap.example.com", 993)
imap.login("user@example.com", "password")

# 2. メールボックス選択と検索
imap.select("INBOX")
stat, data = imap.search(None, "ALL")

# 3. 最新メールのIDを取得
latest_email_id = data[0].split()[-1]

# 4. データの取得と解析
stat, msg_data = imap.fetch(latest_email_id, "(RFC822)")
raw_email = msg_data[0][1]
msg = message_from_bytes(raw_email, policy=policy.default)

print(f"件名: {msg['Subject']}")
imap.logout()

コード全文

「特定の件名」を含むメールを検索し、その本文(テキストまたはHTML)を表示する実用的なコードレシピです。

import imaplib
from email import message_from_bytes, policy
import chardet # 文字コード判定用(なくても動作は可能ですがあると便利)

def receive_email_imap():
    """
    IMAP4を使用してメールを受信し、内容を表示するデモ関数。
    """
    # 接続設定 (Gmailの例)
    imap_server = "imap.gmail.com"
    imap_port = 993
    username = "your_email@gmail.com"
    password = "your_app_password" # アプリパスワード推奨

    try:
        # 1. SSL接続の確立
        print("サーバーに接続中...")
        imap = imaplib.IMAP4_SSL(imap_server, imap_port)

        # 2. ログイン
        imap.login(username, password)
        print("ログイン成功")

        # 3. メールボックスの選択 ('INBOX' は受信トレイ)
        imap.select("INBOX")

        # 4. メールの検索
        # 第1引数は文字セット(Noneまたは"UTF-8")
        # 第2引数は検索条件 ("ALL", "UNSEEN", '(SUBJECT "Test")' など)
        print("メールを検索中...")
        # 例: 件名に "Test" を含むメールを検索
        search_criteria = '(SUBJECT "Test")'
        # search_criteria = "ALL" # 全件の場合
        
        status, messages = imap.search("UTF-8", search_criteria)
        
        email_ids = messages[0].split()
        if not email_ids:
            print("該当するメールが見つかりませんでした。")
            return

        # 最新のメール1件だけを処理(IDリストの最後)
        latest_id = email_ids[-1]
        print(f"メールID {latest_id.decode()} を取得します...")

        # 5. メール実データの取得 (RFC822形式 = ヘッダー+本文の全て)
        status, msg_data = imap.fetch(latest_id, "(RFC822)")

        # 6. 生データからEmailMessageオブジェクトへ変換
        # msg_data[0] は (b'シーケンス番号 (RFC822 {サイズ}', b'メール本体') のタプル
        raw_email = msg_data[0][1]
        
        # policy.defaultを指定することで、件名のデコード等を自動化
        msg = message_from_bytes(raw_email, policy=policy.default)

        # 7. 内容の表示
        print("-" * 30)
        print(f"From    : {msg['From']}")
        print(f"To      : {msg['To']}")
        print(f"Subject : {msg['Subject']}")
        print("-" * 30)

        # 本文の取得(マルチパート対応)
        body = ""
        if msg.is_multipart():
            for part in msg.walk():
                content_type = part.get_content_type()
                content_disposition = str(part.get("Content-Disposition"))

                # 添付ファイルを除外し、テキスト/HTMLを取得
                if "attachment" not in content_disposition:
                    if content_type == "text/plain":
                        try:
                            body = part.get_content()
                        except:
                            pass # デコードエラー等はスキップ
        else:
            # シングルパートの場合
            body = msg.get_content()

        print("--- 本文 (抜粋) ---")
        print(body[:200] + "..." if len(body) > 200 else body)

    except Exception as e:
        print(f"エラーが発生しました: {e}")

    finally:
        # 8. ログアウトと切断
        try:
            imap.close() # 選択したメールボックスを閉じる
            imap.logout() # サーバーから切断
            print("ログアウトしました")
        except:
            pass

if __name__ == "__main__":
    receive_email_imap()

カスタムポイント

主要メソッドと役割

imaplib.IMAP4_SSL オブジェクトの主要な操作一覧です。

メソッド引数例処理・意味
imaplib.IMAP4_SSL(host, port)SSL暗号化通信でIMAPサーバーインスタンスを生成します。
login(user, password)ユーザー名とパスワードでサーバーに認証を行います。
select("INBOX")操作対象のメールボックス(フォルダ)を選択します。
search(None, "ALL")条件に一致するメールIDのリストを取得します。
第1引数は文字コード、第2引数は検索クエリです。
fetch(id, "(RFC822)")指定したIDのメールデータを取得します。
RFC822を指定するとメール全体を取得できます。
logoutなしサーバーとの接続を終了します。

検索条件の指定 (search)

search メソッドは強力ですが、クエリの書き方に注意が必要です。

  • 全件取得: imap.search(None, "ALL")
  • 未読のみ: imap.search(None, "UNSEEN")
  • 件名検索: imap.search("UTF-8", '(SUBJECT "検索ワード")')
    • 日本語検索の場合は第1引数に "UTF-8" を指定します。

EmailMessageへの変換

imap.fetch で取得できるのは「バイト列」です。これを Python で扱いやすくするために email モジュールを使います。

  • message_from_bytes(raw, policy=policy.default):
    • これを推奨します。policy.default を指定することで、MIMEヘッダー(=?utf-8?B?...?= 形式の文字列)が自動的にデコードされ、msg['Subject'] でそのまま日本語文字列として取得できるようになります。

注意点

  1. 既読・未読の扱い
    • fetch(RFC822) を指定してデータを取得すると、サーバー側でそのメールは自動的に「既読(SEEN)」になります。
    • 既読にしたくない場合は、(BODY.PEEK[]) を使用してデータを取得します。
  2. セキュリティ
    • Gmailなどの主要プロバイダは、通常のログインパスワードでのIMAP接続を禁止している場合があります。その場合、2段階認証を有効にし、「アプリパスワード」を発行して使用してください。
  3. メールボックス名
    • 日本語環境のOutlookなどでは、INBOX 以外のフォルダ名が日本語(例: "送信済みアイテム")の場合があります。その際はUTF-7 (&Tgtm+DBN-) 形式などにエンコードが必要な場合があります。

応用

メールに添付ファイルがある場合、それを保存する応用例です。

import os
# (imap接続、msg取得まではコード全文と同様)

def save_attachments(msg, download_folder="downloads"):
    os.makedirs(download_folder, exist_ok=True)

    for part in msg.walk():
        # content_dispositionに "attachment" が含まれていれば添付ファイル
        if part.get_content_maintype() == 'multipart':
            continue
        if part.get('Content-Disposition') is None:
            continue

        filename = part.get_filename()
        if filename:
            filepath = os.path.join(download_folder, filename)
            with open(filepath, 'wb') as f:
                f.write(part.get_payload(decode=True))
            print(f"添付ファイルを保存しました: {filepath}")

まとめ

Pythonでメールを受信する際は、imaplib で通信を行い、email モジュールでデータを解析するという役割分担を理解することが重要です。

特に policy.default を使用したパースを行うことで、複雑な文字コード処理を自動化し、シンプルなコードでメール操作を実現できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次