GMail APIとPandasでメールボックスの大掃除

Googleアカウントの保存ポリシー変更の件、みなさんいかがされていますか?

Google ストレージの仕組みに関する今後の変更 - Gmail ヘルプ

Google Oneも契約しているので容量には余裕があるのですが、GMailには二度どころか一度も読むことのないようなメールが山と溜まっています。年末にもなりましたし掃除でもするかとポチポチ消しはじめ、 楽○さんやY○hooさんが熱心にお勧めしてくださっている人生を豊かにするサービスや商品に関するメールを心苦しながら読まぬうちに削除し、 Promotionカテゴリ下に28,000件あった未読メールのうち、14,000件まで減りましたが、 はて、それでも14,000件ある…残りがどこから来たのか気になったので分析することにしました。

GMail削除UI改善のお知らせ

これまでGMailの検索結果の「全部選択」はリスト表示されているページのメールだけがまとめて選択できたのはご存知でしょうか?本日付で試してみたところ「全部選択」後に選択範囲を「検索結果全体に拡張」できるようになっていたので、まとめて削除が大変捗るようになっています。

Work Package

  • GMail APIでメールリストを取得
  • Pandasで前処理
  • グラフ化

環境

  • Python 3.7
  • Windows 10
  • Scoop
    • wget

GMail APIでメールリストを取得

準備

Python Quickstart  |  Gmail API  |  Google Developersに従って何も迷うことはありません。 APIを有効化し、Desktopアプリ用のcredential.jsonを取得します。これはPythonコードと同じディレクトリに置きましょう。

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
wget https://raw.githubusercontent.com/googleworkspace/python-samples/master/gmail/quickstart/quickstart.py
python quickstart.py

スクリプト実行の際、ブラウザが開いて認証をするとtoken.pickleが保存され、以降のアクセスではこれを使うことができるようになります。

メールリスト取得

コードの全体はこちら⇒Fetch GMail List

メールのリストを取得してmessage.jsonというファイルに保存します。

メールIDの取得

messages().listのAPIでは、GMail内部で割り振られたメールIDを取得できます。

広告に分類されたメールを引っこ抜きます。リファレンスではlistでは好きな件数だけ情報を引き抜けるような事になっているようですが、実態としては1回のリクエストで500件までのようです。

try:
    maxResults = 500
    request = service.users().messages().list(userId='me', labelIds=['CATEGORY_PROMOTIONS'], maxResults=maxResults)
    while request is not None:
        results = request.execute()
        consumed_token += 5
        messages = results.get('messages', [])
        db += messages
        request = service.users().messages().list_next(request, results)
finally:
    df = pd.DataFrame(db)
    df['from'] = pd.Series(dtype='string')
    df['subject'] = pd.Series(dtype='string')
    df['timestamp'] = pd.Series(dtype='string')

    df.to_json('messages.json', orient='records')

メールヘッダー情報の取得: 'From', 'Subject', 'Date'

APIを叩ける回数を調べると1秒間に50件のメール情報まで引き抜けるようでしたので、ちょっとSleepさせてます。(Usage limits  |  Gmail API  |  Google Developers)

try: 
    for i in tqdm.tqdm(df.index):
        if not pd.isnull(df['from'][i]):
            # Skip fetched data
            continue
        result = service.users().messages().get(userId='me', id=df['id'][i], format='metadata', 
                                                metadataHeaders=['From','Subject','Date']).execute()
        consumed_token += 5
        body = result.get('payload',[])

        if not body:
            print("Token Limit per minutes may be reached")
            break

        for el in body['headers']:
            if el['name'] == 'From':
                df.loc[i, 'from'] = el['value']
            elif el['name'] == 'Subject':
                df.loc[i,'subject'] = el['value']
            elif el['name'] == 'Date':
                df.loc[i, 'timestamp'] = pd.Timestamp(el['value'])
        time.sleep(1/40)
finally:
    df.to_json('messages.json', orient='records')
    print("Consumed Token {}".format(consumed_token))

Pandasで前処理

さて、ここから取得した情報をjupyter notebookで内容分析します。

import numpy as np
import seaborn as sns
import matplotlib.pylab as plt
from matplotlib.ticker import PercentFormatter
import pandas as pd

import re

df = pd.read_json('messages.json')

前処理

まず、From形式情報からemailアドレスを取り出します。

def domain(row):
    m = re.search('\<(.*)\>', row)
    if m is None:
        return row
    return m.group(1)
df['email'] = df['from'].apply(domain)
# HEAT MAPを作るときに使う変数
df['Year'] = df['timestamp'].apply(lambda x: x.strftime('%Y'))
df['Month'] = df['timestamp'].apply(lambda x: x.strftime('%m'))

メール送信者の集計

aggregations = {
  'email': ['count']
}
grouped = df.groupby(['email']).agg(aggregations)
grouped.columns = ["_".join(x) for x in grouped.columns.ravel()]
grouped = grouped.sort_values('email_count', ascending=False).reset_index()

グラフ化

メール送信者のトップ20とメールボックスに占める割合を算出します

grouped["cumpercentage"] = grouped["email_count"].cumsum()/grouped["email_count"].sum()*100

fig, ax = plt.subplots()
ax.bar(grouped.email[0:20], grouped["email_count"][0:20], color="C0")
ax2 = ax.twinx()
ax2.plot(grouped.email[0:20], grouped["cumpercentage"][0:20], color="C1", marker="D", ms=7)
ax2.yaxis.set_major_formatter(PercentFormatter())

ax.tick_params(axis="y", colors="C0")
ax2.tick_params(axis="y", colors="C1")
for tick in ax.get_xticklabels():
    tick.set_rotation(90)
plt.show()

ついでにどのくらい熱心にメールを下さっているのかチェック

p = pd.pivot_table(df[df['email']==grouped['email'][0]], values='id', columns=['Year'] , index=['Month'], aggfunc='count')
sns.heatmap(p, cmap=sns.light_palette("seagreen", as_cmap=True),  linewidths=.5)

4年以上メールくれてる

まとめ

  • トップ20のメール送信者を削除すれば50%くらいが削除できそう
  • 読んでない広告メールは購読解除しよう

No comments:

Post a Comment