見出し画像

Pythonでやってみた5’:週報の作成2(Outlookの予定表から自動作成)

1.概要

 以前「週報の作成」記事を作成しましたが、今回は”前回コード+Outlookの予定表からスケジュールを転記”することで週報を作成します。

2.週報テンプレート(WORD)/予定表の確認

 週報に使用するテンプレートは下記の通りです。

【テンプレート(WORDの構造】
●署名欄と日ごとの記載欄用のテーブルが2つ
●期間は月曜日~日曜日であり、土日は常に休みとする
●文字列置換をしたい箇所に重複しない適当な値をふる

 今回取得するカレンダーの状態は下記の通りです。(タスクボードの中にタイトルと場所が記載されている)

3.アウトプットの確認

 今回の処理ではテンプレWORD×予定表から下記PDFを作成します。

4.コード(変数・関数)の説明

 使用するコードの動作を事前説明します。

4-1.週報の基礎的な動作

 週報の基本処理は前回記事で説明しているためそちらをご参照ください。

【週報の基礎的な処理の一覧】
1.月曜日から日曜日の年月日を取得
2.年を年度に変更
3.文字列置換用の辞書を作成
4.パラグラフ内の文字列置換
5.テーブル内の文字列置換
6.PDF化(docx2pdf)

4-2.予定表の取得:win32com

 前回からの変更点はOutlookの予定表データを抽出して転記することです。詳細は下記記事に記載しておりますのでアウトプットだけ示します。

[IN]
import win32com.client
import datetime
import pandas as pd
 
def get_calender(num=9):
    outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI") 
    folder = outlook.GetDefaultFolder(num) # 9=Outlookの予定表
    return folder

def get_schedules(items, start_date, end_date):
    select_items = [] # 指定した期間内の予定を入れるリスト    
    for item in items:
        if start_date <= item.start.date() <= end_date: #datetime.date型
            select_items.append(item)
    return select_items
 
calender = get_calender(num=9)
items = calender.Items # itemsは登録された予定データ
 
# 予定を抜き出したい期間を指定※カレンダー文字列->datetimeに変換
_Mon = datetime.datetime.strptime(Mon_str, "%Y%m%d")
date_Mon = datetime.date(_Mon.year, _Mon.month, _Mon.day)
_Sun = datetime.datetime.strptime(Sun_str, "%Y%m%d")
date_Sun = datetime.date(_Sun.year, _Sun.month, _Sun.day)
 

schedules = get_schedules(items=items, start_date=date_Mon, end_date=date_Sun)
 
# 抜き出した予定の詳細を表示
columns = ['開始時刻', '終了時刻', 'タイトル', '場所', '本文']
datas = [[datetime.datetime.strftime(sch.start, '%Y%m%d'), datetime.datetime.strftime(sch.end, '%Y%m%d'), 
          sch.subject, sch.location, sch.body] for sch in schedules]

df = pd.DataFrame(datas, columns=columns)

[OUT]

4-3.予定表を本文に記載するための辞書作成

 週報作成の思想は{'WORDに記載の置換文字': '記載したい内容'}の辞書を作り、置換処理をしているため本思想に沿う辞書を作成します。

【辞書の作成要領】
1.予定表データを日付ごとに抽出
2.タイトルのデータを取得して、同日に別の予定がある場合はタイトルを改行でつなぐ
3.{'置換文字':'記載したい文字'}の辞書を作る

[IN]
# Mon_str, Sun_str
_Mon = datetime.datetime.strptime(Mon_str, "%Y%m%d")
date_Mon = datetime.date(_Mon.year, _Mon.month, _Mon.day)

for i in range(1, 6): #月曜日-金曜日
    #日付を取得
    _ = datetime.datetime(int(year_Mon), int(month_Mon), int(day_Mon)+i)
    day = _.strftime("%Y%m%d") #win32で取得した日付と同じ文字列型に変換
    #予定表データ(df)からタイトルを抽出して改行('/n')で繋ぐ
    _df = df[df['開始時刻']==day]
    text = ''
    for _ in _df['タイトル']:
        text += _ + '\n'
    #文字列置換
    body = 'RPA_body' + str(i) #置換用KEY
    subwords3[body] = text #月~金曜日で処理
    
print(subwords3)

[OUT]
{'RPA_body1': '筋トレ\n', 'RPA_body2': '', 'RPA_body3': '読書\n筋トレ\n', 'RPA_body4': '飲み\n仕事\n', 'RPA_body5': ''}

5.完成コード

 完成品コードは下記の通りです。問題なければ予定表のタイトル(件名)が記載された週報が作成されます。
 一番上に説明していないライブラリが存在しますが、Pyinstallerでのexe化用です。exe化したいときにコメントアウトを外せば処理できます。

[In]
# import win32timezone #Pyinstallerで作成したexeを実行するためライブラリ

#週報作成用
import docx
import glob, os, datetime
from docx2pdf import convert 
from typing import Tuple, List, Optional

#Outlookカレンダー用
import win32com.client
import pandas as pd

#事前処理
wordfile = '週報_KIYO.docx' #ファイルを開く
today = datetime.datetime.now() #本日の日付を取得
#ファイル保管用のoutputディレクトリを作成
if not os.path.exists('outputs'):
    os.mkdir('outputs')
    print('outputsフォルダを作成しました')


#今週の月曜日と日曜日を取得(月曜日起点)
def getMonday(date=today) -> Tuple:
    #isocalendar()[0]は年、[1]は週番号、[2]は曜日->
    date_Mon = datetime.datetime.strptime("{} {} {}".format(date.isocalendar()[0], date.isocalendar()[1], 1), "%Y %W %w") #1=月曜日 (年, 週番号, 曜日)で今週月曜日を指定
    date_Sun = datetime.datetime.strptime("{} {} {}".format(date.isocalendar()[0], date.isocalendar()[1], 0), "%Y %W %w") #0=日曜日 (年, 週番号, 曜日)で今週日曜日を指定
    Mon_str = date_Mon.strftime("%Y%m%d")  #%Y%m%dの出力例:20220609
    Sun_str = date_Sun.strftime("%Y%m%d")
    year_Mon, month_Mon, day_Mon = Mon_str[:4], Mon_str[4:6], Mon_str[6:] #年、月、日を分ける
    year_Sun, month_Sun, day_Sun = Sun_str[:4], Sun_str[4:6], Sun_str[6:] #年、月、日を分ける
    return (Mon_str, year_Mon, month_Mon, day_Mon, Sun_str, year_Sun, month_Sun, day_Sun) #月曜・金曜日の年月日、年、月、日を出力

#年度換算:1~3月は1を引く
def year2fisyear(year: str, month: str) -> str:
    if int(month) <=3:
        return f'{int(year) -1}'
    else:
        return f'{int(year)}'

#年月日データを取得
Mon_str, year_Mon, month_Mon, day_Mon, Sun_str, year_Sun, month_Sun, day_Sun = getMonday(today)
fiscalyear = year2fisyear(year=year_Mon, month=month_Mon)

#パラグラフ内で置換したい文字列を辞書に格納
subwords1 = {
   'RPA_年月日':today.strftime("%Y年%m月%d日"),
   'RPA_fiscalyear':fiscalyear,
   'RPA_year1':year_Mon,
   'RPA_mm1':month_Mon,
   'RPA_dd1':day_Mon,
   'RPA_year2':year_Sun,
   'RPA_mm2':month_Sun,
   'RPA_dd2':day_Sun,
}

#パラグラフ内の文字列を置換※年月日のみ
doc = docx.Document(wordfile)
for paragraph in doc.paragraphs: #段落ごとにテキストを抽出
   for key, value in subwords1.items(): #置換対象のキーと値を取得
       if key in paragraph.text: #キーがテキストに含まれているか
           paragraph.text = paragraph.text.replace(key, str(value)) #テキスト内の文字を置換

#テーブル内で置換したい文字列を辞書に格納(日付)
def getweekdates(date=today, subwords2={}):
    for i in range(1, 8): #月曜日1-日曜日7 ※WORD内での取り決め
        mm = 'RPA_mm' + str(i)
        dd = 'RPA_dd' + str(i)
        _ = i
        if _ == 7: 
            i=0 #ISOカレンダーでは0=日曜日のため変換(7→0)
            
        day = datetime.datetime.strptime("{} {} {}".format(date.isocalendar()[0], date.isocalendar()[1], i), "%Y %W %w")
        subwords2[mm], subwords2[dd] = day.month, day.day #辞書に格納
    return subwords2

subwords2 = getweekdates(date=today, subwords2={})

#OUTLOOK処理
def get_calender(num=9):
    outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI") 
    folder = outlook.GetDefaultFolder(num) # 9=Outlookの予定表
    return folder

def get_schedules(items, start_date, end_date):
    select_items = [] # 指定した期間内の予定を入れるリスト    
    for item in items:
        if start_date <= item.start.date() <= end_date: #datetime.date型
            select_items.append(item)
    return select_items
 
calender = get_calender(num=9)
items = calender.Items # itemsは登録された予定データ
 
# 予定を抜き出したい期間を指定※カレンダー文字列->datetimeに変換
_Mon = datetime.datetime.strptime(Mon_str, "%Y%m%d")
date_Mon = datetime.date(_Mon.year, _Mon.month, _Mon.day)
_Sun = datetime.datetime.strptime(Sun_str, "%Y%m%d")
date_Sun = datetime.date(_Sun.year, _Sun.month, _Sun.day)
 

schedules = get_schedules(items=items, start_date=date_Mon, end_date=date_Sun)
 
# 抜き出した予定の詳細を表示
columns = ['開始時刻', '終了時刻', 'タイトル', '場所', '本文']
datas = [[datetime.datetime.strftime(sch.start, '%Y%m%d'), datetime.datetime.strftime(sch.end, '%Y%m%d'), 
          sch.subject, sch.location, sch.body] for sch in schedules]
df = pd.DataFrame(datas, columns=columns)


#テーブル内で置換したい文字列を辞書に格納(業務内容)
def post_schedules(df = df, year_Mon=year_Mon, month_Mon=month_Mon, day_Mon=day_Mon, subwords3={}):
    #subwords2から今週月~金曜日までの月、日を取得
    for i in range(1, 6): #月曜日-金曜日
        #日付を取得
        _ = datetime.datetime(int(year_Mon), int(month_Mon), int(day_Mon)+i)
        day = _.strftime("%Y%m%d") #win32で取得した日付と同じ文字列型に変換
        #予定表データ(df)からタイトルを抽出して改行('/n')で繋ぐ
        _df = df[df['開始時刻']==day]
        text = ''
        for _ in _df['タイトル']:
            text += _ + '\n'
        #文字列置換
        body = 'RPA_body' + str(i) #置換用KEY
        subwords3[body] = text #月~金曜日で処理
    return subwords3

subwords3 = post_schedules(df = df, year_Mon=year_Mon, month_Mon=month_Mon, day_Mon=day_Mon, subwords3={})

table = doc.tables[1] #WORDの押印箇所(1つ目のテーブル)が[0]のため本文テーブルは[1]

def tablereplace(table, subwords):
    for row in table.rows:
        for cell in row.cells:
            for key, value in subwords.items():
                if key in cell.text:
                    cell.text = cell.text.replace(key, str(value))
                    
tablereplace(table, subwords2)                    
tablereplace(table, subwords3)   

filepath = f'outputs/{os.path.basename(wordfile)[:-9]}({Mon_str}-{Sun_str})_KIYO.docx'
doc.save(filepath)                 

#PDF化
from docx2pdf import convert 
convert(filepath)

[OUT]

6.参考:コード配布_Pyinstaller

 Pythonが使用できない方にも使用できるようexe化での配布(Pyinstaller)を使用してみました。作成した時の環境でexe化するとエラーが出たため手順が少し複雑になりました。
 ※自分で使う分のチェックはしましたが配布時にEXEファイルがウイルス検知されて使えない可能性もありますのでご注意ください。

6-1.仮想環境の作成/実行

 まず初めにAnaconda Navigatorを使用して仮想環境を作成します。作成方法は「AnacondaでGUIだけで仮想環境を構築」を参照しました。
 今回は「note1weekly」という仮想環境を作成しました。

 次に仮想環境に入ります。入り方は①ターミナルから操作(下記参照)、②Anaconda nabigatorの▶を押して"Open Terminal"を選択します。環境下に入れたらTerminalの左が仮想環境名になります。

[Terminal]
conda activate <仮想環境名>

6-2.外部ライブラリのインストール/事前調整

 新規の仮想環境には外部ライブラリがないためインストールします。
(※ipykernelはJupyterで動作確認用:Jupyterを使用しなければ不要)

[requirements.txt] ※numpy, matplotlib,japanize-matplotlibはなくても動きます(ただ入れただけ)
pandas
numpy
matplotlib
japanize-matplotlib
python-docx
pywin32
docx2pdf
pyinstaller
ipykernel
[Terminal]
pip install -r requirements.txt

 上記の時点で基本的には環境構築は完了のためEXE化できると思います。もしEXE化して実行後にエラーが出た場合は(実行環境が崩れる可能性がありますが)下記の通りpywin32を一度アンインストールしてconda installで再インストールすることでエラーを防げるはずです。

[terminal]
pip uninstall pywin32
conda install pywin32

6-3.コードの修正

 完成コードをスクリプト化(コードをコピペして.pyで作成)してexe化すると下記エラーが出ました。

 

 結論として完成コードの一番上に「import win32timezone」と記載することでエラーが改善できます。

6-4.Exe化:Pyinstaller

 "weeklyreport.py"という名前でスクリプト作成しましたのでexe化は下記の取ります。完了すると"dist"フォルダにEXEファイルが作成されます。

[Terminal]
pyinstaller weeklyreport.py --onefile --noconsole

6-5.検証

 exeファイルとテンプレワードを同じ作業ディレクトリ(下図はDesktop)に配置してexeファイルをダブルクリックすると"outputs"フォルダが作成され、その中に3章と同じアウトプットがあることを確認できました。


参考記事

あとがき

 EXE化できたけどいまだに環境構築は苦手だな・・・・・

この記事が気に入ったらサポートをしてみませんか?