AI研究所 - AI(人工知能)に脅かされないために、AI(人工知能)を作る側の人間になる -

HOME  >  Pythonの便利な使い方シリーズ② 機械学習だけじゃない! ~会社の雑務をPythonで自動化する?!

公開日:2019.03.13 [最終更新日]2019.03.15

Pythonの便利な使い方シリーズ② 機械学習だけじゃない! ~会社の雑務をPythonで自動化する?!

カテゴリー: AI(人工知能)の作り方

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 で報告するための文を作るメソッドを書いていきます。

タグ:


参考になったら「いいね!」と「シェア」をお願いします!!

このエントリーをはてなブックマークに追加
AI(人工知能)セミナー開催中
AI(人工知能)入門セミナー AI(人工知能)入門セミナー IoT入門セミナー

AI入門ブログの中の人

AI研究所 研究スタッフ
通称:マサミ
アメリカ・サンフランシスコにある情報メディアの専門学校を卒業。大手金融会社での事務経験を経て、2016年9月よりAI研究所に入所。見習い研究員として、AI技術を日々勉強中。

Facebookページ