0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

開志専門職大学情報学部Advent Calendar 2023

Day 21

GmailAPIを用いてメール本文の項目をCSV化

Posted at

はじめに

ついに、地方の娯楽施設にも手軽にオンライン予約が導入される時代になってきましたね。
私はそんな娯楽施設でアルバイトをしているのですが、オンライン予約が従来のシステムと連結していないので、めんどくさい!
なので、Gmailの本文内の欲しい情報が汎用性の高いファイルになって存在していれば、従来のシステムもアップデートしてくれるかなぁという淡い希望の元、作成しました。
私生活の中で使える場面は少ないと思いますが、刺さる人がいればと思って紹介します。

動作環境

Python 3.11
GmailAPI v1
pip 23.3.1

Google Cloud Platformとは?

Googleが提供するクラウドコンピューティングサービスの総称です。
Google Cloud Platform(GCP)は、コンピューティング、ストレージ、データベース、ネットワーク、分析、機械学習、AI、IoT、セキュリティなど、さまざまなサービスを提供してます。
今回はGCP内にあるGmailAPIのサービスを使ってGmailのメールを取得します。

料金について

詳しい料金はこちらに載っていますが、今回関係しそうなところは200万回APIが叩ける(1ヶ月あたり)ところだと思います。

GCP側の準備

大まかな流れとしては、GCPにアクセスして、プロジェクトを作り、APIを有効にし、tokenを取得するです。
準備工程を細かく書くのは長いので、僕が参考にした他の記事を載せておきます
参考資料はこちら
また、途中でスコープ(操作できる範囲)を設定するところがありますが、それはどう使用したいかに合わせてこちらのv1を参考にして選んでください。
僕は全ての権限を与えちゃいました。

作成したコード

とりあえず作成したコードを載せます。

from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
import json
import base64
import csv
import re

def reserve(body_content):
    # 日付を抽出するための正規表現パターンを定義
    date_pattern = re.compile(r'\d{4}年\d{2}月\d{2}日')  # 日付のフォーマットに合わせて調整してください
    #csvで別セルで扱うなら'年'、'月'、'日'を','へ変換してください

    # 本文から日付パターンを検索
    match = date_pattern.search(body_content)

    # マッチが見つかれば、マッチした日付を返す
    if match:
        return match.group()

    # マッチが見つからなければ、Noneを返す
    return None

def quantity(body_content):
    # 人数を抽出するための正規表現パターンを定義
    date_pattern = re.compile(r'(\d+)名')

    # 本文から人数パターンを検索
    match = date_pattern.search(body_content)

    # マッチが見つかれば、マッチした人数を返す
    if match:
        return match.group()

    # マッチが見つからなければ、Noneを返す
    return None

def delegate(body_content):
    # 名前を抽出するための正規表現パターンを定義
    date_pattern = re.compile(r'([ァ-ヴー]\S+ [ァ-ヴー]\S+)様')
    #現在カタカナで入力された名前だけですが、英語を追加するときは[]内にァ-ヴーA-Za-zと変更してください

    # 本文から名前パターンを検索
    match = date_pattern.search(body_content)

    # マッチが見つかれば、マッチした名前を返す
    if match:
        return match.group()

    # マッチが見つからなければ、Noneを返す
    return None

def seat(body_content):
    # 席を抽出するための正規表現パターンを定義
    date_pattern = re.compile(r'(\d+)号室')

    # 本文から席パターンを検索
    match = date_pattern.search(body_content)

    # マッチが見つかれば、マッチした席を返す
    if match:
        return match.group()

    # マッチが見つからなければ、Noneを返す
    return None

header = ['日付', '差出人', '宛先', '件名', '本文', '時間', '人数', '代表者名', '部屋']
        
with open('Gmail.csv', 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow(header)

def get_header(headers, name):
    for h in headers:
        if h['name'].lower() == name:
            return h['value']


def base64_decode(data):
    return base64.urlsafe_b64decode(data).decode()


def base64_decode_file(data):
    return base64.urlsafe_b64decode(data.encode('UTF-8'))


def get_body(body):
    if body['size'] > 0:
        return base64_decode(body['data'])


def get_parts_body(body):
    if (body['size'] > 0
            and 'data' in body.keys()
            and 'mimeType' in body.keys()
            and body['mimeType'] == 'text/plain'):
        return base64_decode(body['data'])


def get_parts(parts):
    for part in parts:
        if part['mimeType'] == 'text/plain':
            b = base64_decode(part['body']['data'])
            if b is not None:
                return b
        if 'body' in part.keys():
            b = get_parts_body(part['body'])
            if b is not None:
                return b
        if 'parts' in part.keys():
            b = get_parts(part['parts'])
            if b is not None:
                return b


def get_attachment_id(parts):
    for part in parts:
        if part['mimeType'] == 'image/png':
            return part['body']['attachmentId'], 'png'
    return None, None

def main():
    scopes = ['https://mail.google.com/']
    creds = Credentials.from_authorized_user_file('token.json', scopes)
    service = build('gmail', 'v1', credentials=creds)

    messages = service.users().messages().list(
        userId='me',
        q='from: 自分のメールアドレス'
    ).execute().get('messages')

    for message in messages:
        print('=' * 10)
        m_data = service.users().messages().get(
            userId='me',
            id=message['id']
        ).execute()

        # ヘッダー情報
        headers = m_data['payload']['headers']

        # 日付
        message_date = get_header(headers, 'date')
        print(f'日付: {message_date}')

        # 差出人
        from_date = get_header(headers, 'from')
        print(f'差出人: {from_date}')

        # 宛先
        to_date = get_header(headers, 'to')
        print(f'宛先: {to_date}')

        # 件名
        sub_date = get_header(headers, 'subject')
        print(f'件名: {sub_date}')

        body = m_data['payload']['body']
        body_data = get_body(body)

        parts_data = None
        if 'parts' in m_data['payload'].keys():
            parts = m_data['payload']['parts']
            parts_data = get_parts(parts)
            # 添付ファイル
            attachment_id, extension = get_attachment_id(parts)
            if attachment_id is not None:
                res = service.users().messages().attachments().get(
                    userId='me',
                    messageId=message['id'],
                    id=attachment_id
                ).execute()
                f_data = base64_decode_file(res['data'])

                with open(f'./Downloads.{extension}', 'wb') as f:
                    f.write(f_data)

        body_result = body_data if body_data is not None else parts_data
        print(f'本文: {body_result}')

        # メールの本文から日付を抽出
        reserve_date = reserve(body_result)
        quantity_date = quantity(body_result)
        delegate_date = delegate(body_result)
        seat_date = seat(body_result)

        # CSVコンテンツに抽出された要素を追加
        body_content = [
            [message_date, from_date, to_date, sub_date, body_result, reserve_date, quantity_date, delegate_date, seat_date],
        ]

        with open('Gmail.csv', 'a', newline='', encoding='utf-8') as f:
            writer = csv.writer(f)
            writer.writerows(body_content)

if __name__ == '__main__':
    main()

やっていることとしては、上の4つの関数でメール本文からほしい形式をre.compileの部分で設定してあげると、メール本文からその形の文章をCSV内の別ヘッダに保存されます。
他に追加で取りたい情報をヘッダに増やしたい時はこちらにあるGmailで使える演算子を元に作成することができます。

まとめ

今回初めてこういった自動化をやってみました。
Pythonも触れることはほぼ初めてだったこともあって、作成にすごく時間がかかりましたが、なんとか僕が求めるレベルはできたのではないかと思います。
また、ひと月で200万回メールを受け取ることも極めて少ないと思うので、無料版でも十分じゃないかなと思います。
使い所を考えるなら、家計簿とかを自動でつけてもらう時に、よく使うサービスのメールアドレス(ウーバーとか?)を登録しておけば、使った金額だけ出したりできるかもしれないですね。
今後はそういった方面もやってみてもいいかもしれません...

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?