
Python大好きなAI研究所のショウと申します。
前回に引き続き機械学習だけではないPythonの実用的な使い方をお伝えしたいと思います!
今回のテーマは、「Pythonを利用して、Gmailから抽出した依頼メールを自動的にタスク分けし業務ステータスごとに分類。本日のやることリストをSlackに送る」です。
第一回の記事はこちら
前回の記事の簡単なおさらいを見てみましょう。
Section 1 でGmailをPythonで扱えるようにして、
Section 2 でGoogleスプレッドシートをPythonで扱えるようにしました。
Section 3 でPythonからSlackへメッセージを送れるようにして、
Section 4 でPythonの必要なモジュール等をインストールし、準備完了です。
今回はここから、ようやくPythonのコードを書いていきます。
それでは、始めましょう !
まずは、コードの前に概要を説明します。
其の一 : Googleにアクセスし、ユーザー認証データを取得し、認証を実行します。
其の二 : Gmailのサービスを取得し、GmailのAPIが使えるようにします。
其の三 : メッセージの一覧を取得し、該当するメッセージを振り分けます。
其の四 : そのメッセージを分割し、タスク分けをして、スプレッドシートに転記します。
其の五 : 分割した本文とスプレッドシートの内容から、Slackに送るテンプレート文を作成します。
其の六 : Slackにメッセージを送ります。
今回はこの中でも、其の三までの内容をやっていきます。
コードをコピー&ペーストして動作を確認しつつ、是非チャレンジしてみてください。
(1) 作業フォルダを作ります。
どこか適当な場所に(デスクトップなど)フォルダーを適当な名前で作り、そのフォルダーの中に、前回ダウンロードしたJSONファイルを格納します。
(GmailとGspreadの分)
(2) フォルダとファイル構成
まず適当なエディターを開いて下さい。
私の場合は Visual Studio Code を使っています。
AIセミナー(https://ai-kenkyujo.com/seminar/)では
Pycharmを使っていますね。
エディターは使い慣れたもので構いません。
メインの処理をするmain.pyとサブ的なsub.pyというファイルを作成します。名前は任意でつけています。
こんな感じのイメージです。
(3) コードを書いていきます
3-1 「main.py」を書いていきます。
コードはこちらの記事を参考にして、自分流に変更しています。
プログラムを書く際には、先人が作っているコードを参考にすることで非常に勉強になることが多いです。
ぜひ皆さんもいろいろな記事やコードを見て、真似をして勉強してみてください!
前置きはさておき、一度全てのコードを記載します。
その後にかんたんな説明をしていきます。
import httplib2, os from apiclient import discovery from oauth2client import client, tools from oauth2client.file import Storage import base64, email import dateutil.parser from datetime import datetime from dateutil.relativedelta import relativedelta # sub.pyからupdate_sheet()を呼び込む。 from sub import update_sheet import requests import json # Gmail権限のスコープを指定 SCOPES = 'https://www.googleapis.com/auth/gmail.readonly' # 認証ファイル CLIENT_SECRET_FILE = 'client_id.json' USER_SECRET_FILE = 'credentials-gmail.json' # ------------------------------------ # ユーザ認証データの取得 def gmail_user_auth(): store = Storage(USER_SECRET_FILE) credentials = store.get() if not credentials or credentials.invalid: flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) flow.user_agent = 'Python Gmail API' credentials = tools.run_flow(flow, store, None) print('認証結果を保存しました:' + USER_SECRET_FILE) return credentials # Gmailのサービスを取得 def gmail_get_service(): credentials = gmail_user_auth() http = credentials.authorize(httplib2.Http()) service = discovery.build('gmail', 'v1', http=http) return service # ------------------------------------ def email_extract_text(raw): # Emailを解析する eml = email.message_from_bytes(raw) # 件名を取得 subject = '' lines = email.header.decode_header(eml.get('Subject')) for frag, encoding in lines: if encoding: sub = frag.decode(encoding) subject += sub else: if isinstance(frag, bytes): sub = frag.decode('iso-2022-jp') else: sub = frag subject += sub # 差出人を取得 addr = '' lines = email.header.decode_header(eml.get('From')) for frag, encoding in lines: if encoding: sub = frag.decode(encoding) addr += sub else: if isinstance(frag, bytes): addr = frag.decode('iso-2022-jp') else: sub = frag addr += sub print("-----------") print("From: " + addr) # 本文を取得 body = "" for part in eml.walk(): if part.get_content_type() != 'text/plain': continue # ヘッダを辞書型に落とす head = {} for k,v in part.items(): head[k] = v s = part.get_payload(decode=True) # 文字コード if isinstance(s, bytes): charset = part.get_content_charset() or 'iso-2022-jp' s = s.decode(str(charset), errors="replace") body += s print("Body: " + body) # 日付 date = dateutil.parser.parse(eml.get('Date')).strftime("%Y/%m/%d %H:%M:%S") # 件名と本文を結果とする # 扱いやすくするために辞書にして値を返す。 return {'From':addr,'Date':date,'Subject':subject,'Body':body} # Slackにメッセージを送るための関数 def send_slack(task4mail,to_boss): task4mail_text='\n'.join(task4mail) to_boss_text='\n'.join(to_boss) send_slack=f'昨日深夜から今朝までの入稿件数は以下になります。\ \n\n{task4mail_text}\n\n本日の業務日程は以下になります。\ \n\n{to_boss_text}\n\n以上です。' post_url='Slackで設定したウェブホックのURLをここに記入' requests.post(post_url, data=json.dumps({"text": send_slack, "username": 'あなたの名前', "icon_emoji": ":python:"})) def receive_and_send_slack(count): today=datetime.today() # 12時間前のメールから検索 halfday=today+relativedelta(days=-0.5) # GmailのAPIが使えるようにする service = gmail_get_service() # メッセージを扱うAPI messages = service.users().messages() # 自分のメッセージ一覧を得る msg_list = messages.list(userId='me', maxResults=count).execute() for i, msg in enumerate(msg_list['messages']): msg_id = msg['id'] print(i, "=", msg_id) m = messages.get(userId='me', id=msg_id, format='raw').execute() raw = base64.urlsafe_b64decode(m['raw']) # 顧客からの納品依頼のみを検出 text_dict = email_extract_text(raw) if text_dict['From']=='' and \ '記事納品依頼' in text_dict['Subject'] and \ datetime.strptime(text_dict['Date'], '%Y/%m/%d %H:%M:%S')>halfday: task4mail,to_boss=update_sheet(text_dict) # Slackにメッセージを送るための関数 send_slack(task4mail,to_boss) break if __name__ == '__main__': # 毎朝の業務で直近のメールでいいので、10件に設定。 receive_and_send_slack(10)
一行目から説明していきます。
import httplib2, os from apiclient import discovery from oauth2client import client, tools from oauth2client.file import Storage import base64, email import dateutil.parser from datetime import datetime from dateutil.relativedelta import relativedelta # sub.pyからupdate_sheet()を呼び込む。 from sub import update_sheet import requests import json
必要なライブラリをインストールしていきます。
後で内容を書いていく「sub.py」から、
スプレッドシートの内容をアップデートする関数、
update_sheetを呼び込みます。
Pythonではライブラリを呼び出す際、
実行ファイル名(〇〇.py)の
.pyの部分を抜かして、importします。
# Gmail権限のスコープを指定 SCOPES = 'https://www.googleapis.com/auth/gmail.readonly' # 認証ファイル CLIENT_SECRET_FILE = 'client_id.json' USER_SECRET_FILE = 'credentials-gmail.json'
Google 管理コンソールで作業を行えるようにする為の、管理者の役割を与えます。
USER_SECRET_FILE の credentials-gmail.json は、
ユーザ認証データの取得を行えば、フォルダー内に作られます。
# ユーザ認証データの取得 def gmail_user_auth(): store = Storage(USER_SECRET_FILE) credentials = store.get() if not credentials or credentials.invalid: flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) flow.user_agent = 'Python Gmail API' credentials = tools.run_flow(flow, store, None) print('認証結果を保存しました:' + USER_SECRET_FILE) return credentials
USER_SECRET_FILEから認証情報を取得する。
もしその情報がなければ、CLIENT_SECRET_FILEから情報を取得して、
新たなcredentialsを作り、USER_SECRET_FILEを指定した名前で保存し、
次から使えるようにします。
実行すると一回目はブラウザが起動し、
メールアドレスとパスワードを求められます。
# Gmailのサービスを取得 def gmail_get_service(): credentials = gmail_user_auth() http = credentials.authorize(httplib2.Http()) service = discovery.build('gmail', 'v1', http=http) return service
GoogleのAPIを使えるようにする。
def email_extract_text(raw): # Emailを解析する eml = email.message_from_bytes(raw) # 件名を取得 subject = '' lines = email.header.decode_header(eml.get('Subject')) for frag, encoding in lines: if encoding: sub = frag.decode(encoding) subject += sub else: if isinstance(frag, bytes): sub = frag.decode('iso-2022-jp') else: sub = frag subject += sub
メール本文を得るために、
rawフォーマット(Emailそのままの形式)でデータを取得し、
Emailモジュールを使って、メールを解析します。
まずは空のsubjectという変数を用意して、
文字コードを判定しながら、件名を取得します。
iso-2022-jp とは電子メールなどで利用される、日本語文字コードの一種のことです。
# 差出人を取得 addr = '' lines = email.header.decode_header(eml.get('From')) for frag, encoding in lines: if encoding: sub = frag.decode(encoding) addr += sub else: if isinstance(frag, bytes): addr = frag.decode('iso-2022-jp') else: sub = frag addr += sub print("-----------") print("From: " + addr)
同じく空のaddrという変数を用意して、
文字コードを判定しながら、差出人を取得します。
# 本文を取得 body = "" for part in eml.walk(): if part.get_content_type() != 'text/plain': continue # ヘッダを辞書型に落とす head = {} for k,v in part.items(): head[k] = v s = part.get_payload(decode=True) # 文字コード if isinstance(s, bytes): charset = part.get_content_charset() or 'iso-2022-jp' s = s.decode(str(charset), errors="replace") body += s print("Body: " + body) # 日付 date = dateutil.parser.parse(eml.get('Date')).strftime("%Y/%m/%d %H:%M:%S") # 件名と本文を結果とする # 扱いやすくするために辞書にして値を返す。 return {'From':addr,'Date':date,'Subject':subject,'Body':body}
同じく空のbodyという変数を用意して、
walk()メソッドはメッセージオブジェクトツリー中のすべての
part および subpart をわたり歩くために使います。
日付も取得したのち、扱いやすいように辞書型にして返します。
# Slackにメッセージを送るための関数 def send_slack(task4mail,to_boss): task4mail_text='\n'.join(task4mail) to_boss_text='\n'.join(to_boss) send_slack=f'昨日深夜から今朝までの入稿件数は以下になります。\ \n\n{task4mail_text}\n\n本日の業務日程は以下になります。\ \n\n{to_boss_text}\n\n以上です。' post_url='Slackで設定したウェブホックのURLをここに記入' requests.post(post_url, data=json.dumps({"text": send_slack, "username": 'あなたの名前', "icon_emoji": ":python:"}))
のちほど内容を書く sub.py の update_sheet から task4mailとto_bossを取得して、2つを報告分として、まとめてからrequestsライブラリのpostメソッドにjson形式で渡します。
Slackにメッセージが送れるようにするための関数。
def receive_and_send_slack(count): today=datetime.today() # 12時間前のメールから検索 halfday=today+relativedelta(days=-0.5) # GmailのAPIが使えるようにする service = gmail_get_service() # メッセージを扱うAPI messages = service.users().messages() # 自分のメッセージ一覧を得る msg_list = messages.list(userId='me', maxResults=count).execute() for i, msg in enumerate(msg_list['messages']): msg_id = msg['id'] print(i, "=", msg_id) m = messages.get(userId='me', id=msg_id, format='raw').execute() raw = base64.urlsafe_b64decode(m['raw'])
こちらがメインの関数。
何件分検索するかを引数として受け取り、今日の日付と時間を メソッドから取得しています。
12時間前のメールから検索できるように12時間前の日付時刻を取得します。
Gmail API を扱えるようにする関数から service オブジェクトを取得し、メッセージを扱う message オブジェクトを取得します。
メッセージ一覧を見ながら、顧客からのメールだけを抽出します。
# 顧客からの納品依頼のみを検出 text_dict = email_extract_text(raw) if text_dict['From']==' ' and \ '記事納品依頼' in text_dict['Subject'] and \ datetime.strptime(text_dict['Date'], '%Y/%m/%d %H:%M:%S')>halfday: task4mail,to_boss=update_sheet(text_dict) # Slackにメッセージを送るための関数 send_slack(task4mail,to_boss) break
メール本文からメールアドレス、件名、日付、本文をキーとした、辞書型を text_dict オブジェクトとして取得します。
件名はテンプレートで決められていると仮定して、件名と日付から二重チェックを行います。
メールの辞書を update_sheet()に渡します。
戻ってきた各変数を send_slack() に渡し、Slackに報告文を送り終了します。
一つ前のメールを取得しないように break文で for 文のループから抜けます。
if __name__ == '__main__': # 毎朝の業務で直近のメールでいいので、10件に設定。 receive_and_send_slack(10)
if __name__ == ‘__main__’は
「該当のファイルがコマンドラインからスクリプトとして実行された場合にのみ以降の処理を実行する」という意味となります。
$ python 実行ファイル名.py
これがコマンドラインから実行した例。
他のファイルからインポートされたとき__name__には <モジュール名>が入ります。
コマンドラインから実行したとき __name__は__main__になるイメージですね。
要するに直接実行された場合のみ実行し、それ以外の場合は実行しないという意味です。
長くなりましたが、第二回はここまです。
最後の第三回では、情報をスプレッドシートに転記して、Slack で報告するための文を作るメソッドを書いていきます。