PythonでGmailからメールを取得してExcelに保存する自動化スクリプト

### PythonでGmailからメールを取得してExcelに保存する方法

この記事では、Pythonを使ってGmailのメールを取得し、Excelファイルに保存する方法を紹介します。特に、過去に取得したメールを再度保存しないようにする方法や、エラーハンドリング、メールの日時フォーマットの処理を含めて解説します。

#### 使用するライブラリ

- **imaplib**: GmailのIMAPサーバーに接続し、メールを取得するためのライブラリです。
- **email**: メールの解析と操作を行います。
- **openpyxl**: Excelファイルの読み書きに使用します。
- **pytz**: タイムゾーンの操作をサポートします。
- **re**: 正規表現を使用して文字列を処理します。

#### 1. メールの取得

まず、GmailのIMAPサーバーに接続して、メールを取得します。ここでは、全てのメールを取得し、過去に保存したメールをスキップするようにしています。

```python
def fetch_emails(last_saved_date):
    with imaplib.IMAP4_SSL("imap.gmail.com") as mail:
        mail.login(email_user, app_password)
        mail.select("inbox")  # Inboxを選択
        
        status, messages = mail.search(None, "ALL")
        email_ids = messages[0].split()

        emails = []
        for email_id in email_ids:
            status, msg_data = mail.fetch(email_id, "(RFC822)")
            for response_part in msg_data:
                if isinstance(response_part, tuple):
                    msg = email.message_from_bytes(response_part[1])
                    subject, encoding = decode_header(msg["Subject"])[0]
                    if isinstance(subject, bytes):
                        subject = subject.decode(encoding or "utf-8")
                    from_ = msg.get("From")
                    date = msg.get("Date")
                    # メールの日時を解析
                    date_cleaned = re.sub(r" \(.*?\)", "", date)  # "(UTC)" を削除
                    date_cleaned = date_cleaned.replace("GMT", "+0000")  # "GMT" を "+0000" に変換
                    
                    # 日付をパース
                    try:
                        email_date = datetime.strptime(date_cleaned, "%a, %d %b %Y %H:%M:%S %z")
                    except ValueError:
                        print(f"日付解析エラー: {date_cleaned}")
                        continue
                    
                    if email_date <= last_saved_date:
                        continue
                    
                    body = ""
                    if msg.is_multipart():
                        for part in msg.walk():
                            content_type = part.get_content_type()
                            if content_type == "text/plain":
                                body = part.get_payload(decode=True).decode("utf-8")
                                body = extract_reply_body(body)
                                break
                    else:
                        body = msg.get_payload(decode=True).decode("utf-8")
                        body = extract_reply_body(body)
                    
                    emails.append((subject, from_, date, body))
        return emails
```

#### 2. メールの保存

取得したメールをExcelファイルに保存する関数です。ファイルが存在する場合、最後に保存した日時を取得して、それ以降のメールだけを保存します。

```python
def save_to_excel(emails, start_date):
    try:
        wb = openpyxl.load_workbook(excel_file)
        sheet = wb.active
    except FileNotFoundError:
        wb = openpyxl.Workbook()
        sheet = wb.active
        sheet.append(["Subject", "From", "Date", "Body"])

    dates = [row[2] for row in sheet.iter_rows(min_row=2, values_only=True) if row[2]]  # 日付列を取得
    if dates:
        last_saved_date = max(dates)
        if isinstance(last_saved_date, str):
            last_saved_date = datetime.strptime(last_saved_date, "%a, %d %b %Y %H:%M:%S %z")
    else:
        last_saved_date = start_date

    if last_saved_date.tzinfo is None:
        last_saved_date = last_saved_date.replace(tzinfo=datetime.timezone.utc)

    for subject, from_, date, body in emails:
        date_cleaned = re.sub(r" \(.*?\)", "", date)
        date_cleaned = date_cleaned.replace("GMT", "+0000")
        try:
            email_date = datetime.strptime(date_cleaned, "%a, %d %b %Y %H:%M:%S %z")
        except ValueError:
            print(f"日付解析エラー: {date_cleaned}")
            continue
        
        if email_date <= last_saved_date:
            continue
        sheet.append([subject, from_, date, body])

    wb.save(excel_file)
    print("新しいメールをExcelファイルに保存しました。")
```

#### 3. メールの本文から返信部分を削除

返信メールの場合、メール本文に「On ... wrote:」のような返信部分が含まれます。これを削除して、純粋な本文だけを取得します。

```python
def extract_reply_body(body):
    reply_index = body.find("On")  # 通常、返信部分は「On ... wrote:」という形式
    if reply_index != -1:
        return body[:reply_index].strip()
    return body.strip()
```

#### 4. 初回実行時と次回以降の動作

初回実行時には、`start_date` を設定して、その日時以降のメールを取得します。次回以降は、Excelファイルに保存された最終日時を基準に、前回保存した日時以降のメールを取得します。

※データ取得
dates = [row[2] for row in sheet.iter_rows(min_row=2, values_only=True) if row[2]]について
https://qiita.com/ellyyyy/items/b363113f599e67665a4d

```python
if __name__ == "__main__":
    start_date = datetime(2025, 1, 1, 0, 0, 0)

    if start_date.tzinfo is None:
        start_date = start_date.replace(tzinfo=pytz.utc)

    emails = fetch_emails(start_date)
    if emails:
        save_to_excel(emails, start_date)
    else:
        print("新しいメールがありませんでした。")

    try:
        wb = openpyxl.load_workbook(excel_file)
        sheet = wb.active
        dates = [row[2] for row in sheet.iter_rows(min_row=2, values_only=True) if row[2]]
        if dates:
            last_saved_date = max(dates)
            emails = fetch_emails(last_saved_date)
            if emails:
                save_to_excel(emails, last_saved_date)
            else:
                print("新しいメールがありませんでした。")
    except FileNotFoundError:
        print("Excelファイルが見つかりません。最初にメールを保存してください。")
```
### 処理フロー
A[初回実行時: 開始日時指定] --> B[fetch_emails関数]
    B --> C[IMAPサーバーに接続]
    C --> D[メールのリストを取得]
    D --> E[各メールの解析]
    E --> F[日付をパースして保存対象のメールをフィルタリング]
    F --> G[メールの件名、送信者、本文を取得]
    G --> H[メールデータをリストに追加]
    H --> I[保存対象のメールがあればsave_to_excel関数]
    I --> J[Excelファイルを開くまたは新規作成]
    J --> K[ヘッダー追加]
    K --> L[既存のメールの日時を確認]
    L --> M[新しいメールをフィルタリングしてExcelに保存]
    M --> N[Excelファイルを保存]

### まとめ

このコードでは、最初に指定した日時からメールを取得し、その後はExcelファイルに保存されている最終保存日時を基準にして新しいメールを取得・保存します。メールの日時や本文に関するエラーハンドリングも行い、メールの重複保存を防いでいます。

この方法を使えば、Gmailのメールを効率よく管理し、Excelで簡単に保存・整理できます。