見出し画像

Pythonライブラリ(Win32 API):pywin32(Window/Office操作)

1.概要

 Win32 APIにアクセスしてWindowsでできる操作をPythonで実行できるライブラリがpywin32です。
 公式Docsの解読が難しすぎるため、使えそうなやつのみ紹介します。

2.Word

2-1.PDF化:doc.SaveAs(path, FileFormat=17)

 Wordの一括PDF化を紹介します。docx(word2007以降)であれば別記事で紹介した「docx2pdf」が有効ですが、旧拡張子docには使用できません。
 そこでpywin32を使用してPDF化操作を実行します。なおコードは下記記事を参考にしました。

 2-1-1.docファイル(サンプル)の作成

 まずはサンプルファイルを用意します。「python-docxライブラリ」を使用して適当なファイルを作成しました。

[IN]
import docx

def makewordfile(text, filename):
    doc = docx.Document()
    doc.add_paragraph(text)
    doc.save(filename)

for i in range(1,4):
    makewordfile(text=f'Hello world {i}'
                 , filename=f'hello{i}.doc')

[OUT]


 2-1-2.処理コード:Wordの一括PDF化

 処理の流れは下記の通りです。

【処理の流れ】
1.globでdocファイルのパスを抽出
2.取得したパスを絶対パスに変換
 ー>相対パスを使用するとDocumentフォルダにファイルが保存される。
3.PDFへ変換

[IN]
import glob, os 
import win32com.client as win32
 
files = glob.glob('*.doc') #docファイルのみ抽出
print(files)

def doc2pdf(file):
    app = win32.Dispatch("Word.Application") #Wordのインスタンスを生成
    app.Visible = True #GUIでアプリを表示させる
    app.DisplayAlerts = False #警告を表示させない
    
    path_abs = os.path.abspath(file) #絶対パスでないとドキュメントに保存される
    path_pdf = path_abs.replace('.doc', '.pdf') #拡張子をdoc->pdfにしたパス(text)を作成
    doc = app.Documents.Open(path_abs)
    doc.SaveAs(path_pdf, FileFormat=17) #17=PDF
    doc.Close()
    app.Quit()
    
for file in files:
    doc2pdf(file)

[OUT]
['hello1.doc', 'hello2.doc', 'hello3.doc']

3.Excel


3-1.PDF化:book.ExportAsFixedFormat(0, path)

 Excelの一括PDF化は下記記事で紹介しているためコピペを紹介します。

[In]
import win32com.client as win32
import os 

#win32に渡すパスは絶対パスで指定
files = globfiles('note_PDF', ['.txt', '.xlsx', '.docx']) #output->['note_PDF.txt', 'note_PDF.xlsx', 'note_PDF.docx']
files_abs = [os.path.abspath(file) for file in files]


app = win32.Dispatch("Excel.Application") #Excelを起動
app.Visible = True
app.DisplayAlerts = False
path_excel = files_abs[2] #Excelの絶対パスを指定 output->'c:\\Users\\KIYO\\Desktop\\note\\02_pythonライブラリ\\2022年01月_note_PDF\\note_PDF.xlsx''c:\\Users\\KIYO\\Desktop\\note\\02_pythonライブラリ\\2022年01月_note_PDF\\note_PDF.xlsx'
path_pdf = path_excel.replace('.xlsx', '.pdf') #上のパスを拡張子.pdfに変更

book = app.Workbooks.Open(path_excel) #Excelファイルを開く
book.ExportAsFixedFormat(0, path_pdf) #PDFに変換※引数0はPDFファイルに変換を指定

app.Quit() #Excelを終了

3-2.Sharepointへファイル保存:book.SaveAs(url)

 Office365の使用においてデータ保管をクラウド上のSharepointでしている場合、ローカルPCにあるExcelファイルはbook.SaveAs()を用いて可能です。この操作のポイントは下記の通りです。

【本処理のポイント】
●私の会社環境だと、社外からでも本コードでSharepointへ保存可能
●VBAで同様にコピーできるFSOやCopyfileで処理すると社内からでも保存できない

[IN]
import win32com.client as win32
import os

url_share='https://<会社指定のやつ>.sharepoint.com/sites/<ホーム名>/Shared%20Documents/<指定ディレクトリ>'
filepath = 'Book1.xlsm' #追って絶対パスに変換

app = win32.Dispatch("Excel.Application") #Excelを起動
app.Visible = True #Excelを表示:エラー時にGUIで消せるようにする
app.DisplayAlerts = False #警告を表示させない

book = app.Workbooks.Open(os.path.abspath(filepath)) #Excelファイルを開く
book.SaveAs(os.path.join(url_share, filepath)) #保存
app.Quit() #Excelを終了book

[OUT]

4.Outlook

 pywin32でOutlookを使用する場合は"win32.Dispatch("Outlook.
Application").GetNamespace("MAPI")
"でインスタンスを作成して”GetDefaultFolder(<フォルダ番号>)”で使用するアイテムを選択します。
 フォルダ番号は使用する分だけ辞書にすると選択しやすくなります。

[IN]
import win32com.client as win32

foldernames = {'Deleted': 3, #削除済みアイテム
              'SentItems': 5, #送信済みアイテム
              'Inbox': 6, #受信トレイ
              'Calender': 9, #カレンダー
              } 


outlook = win32.Dispatch("Outlook.Application").GetNamespace("MAPI") 
num_Folder = foldernames['Inbox'] #メールボックスを指定
folder = outlook.GetDefaultFolder(num_Folder) #指定のフォルダを取得
items = folder.Items #Itemsプロパティ:フォルダ内のアイテムを取得
print(items.Application)

[OUT]
Outlook

4-1.カレンダー:GetDefaultFolder(9)

 Win32を使用することでOutlookのカレンダー情報を抽出できます。

【使用条件】
●Microsoftのアカウントをもっていること
●(Desktop)アプリのOutlookをインストール/設定済みであること

 自宅PCはOutlook2010しかなくまともに使っていないため、サンプルスケジュールを入れてみました。

 上図の予定表から各予定の情報を抽出しました。

【使用時のポイント】
●GetDefaultFolder(9)を選定することでカレンダー情報を抽出
 ー>数値を変えることで受信/送信フォルダも取得可能(参考記事
●calender.Itemsの各予約の情報がある(今回は初期状態から6個の予定を入れたためデータ数は6)。datetimeによる条件抽出をすることでほしい日時のデータを抽出可能である。
●dir()でitem(1日のデータを保持するインスタンス)の属性を確認してみたが、今回使用した属性が現れなかった(公式Docsのどこで探せばよいかが不明のため追って検索)。


[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
 
calender = get_calender(num=9)
print(calender)
print('データ型:', type(calender))
 
items = calender.Items # itemsは登録された予定データ
print(items)
print('予定データ数', len(items))
print('データ型:', type(items))
 
# 予定を抜き出したい期間を指定
start_date = datetime.date(2022, 6, 6)
end_date = datetime.date(2022, 6, 10)
 
def get_schedules(items, start_date, end_date):
    select_items = [] # 指定した期間内の予定を入れるリスト
    for item in items:
        if start_date <= item.start.date() <= end_date:
            select_items.append(item)
    return select_items
    
schedules = get_schedules(items, start_date, end_date)
 
print(schedules)
print(dir(schedules[0]))
 
# 抜き出した予定の詳細を表示
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)
df


[OUT]
予定表
データ型: <class 'win32com.client.CDispatch'>

<COMObject <unknown>>
予定データ数 6
データ型: <class 'win32com.client.CDispatch'>

[<COMObject <unknown>>, <COMObject <unknown>>, <COMObject <unknown>>, <COMObject <unknown>>, <COMObject <unknown>>, <COMObject <unknown>>]
['_ApplyTypes_', '_FlagAsMethod', '_LazyAddAttr_', '_NewEnum', '_Release_', '__AttrToID__', '__LazyMap__', '__bool__', '__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_builtMethods_', '_enum_', '_find_dispatch_type_', '_get_good_object_', '_get_good_single_object_', '_lazydata_', '_make_method_', '_mapCachedItems_', '_oleobj_', '_olerepr_', '_print_details_', '_proc_', '_unicode_to_string_', '_username_', '_wrap_dispatch_']

4-2.メール情報:GetDefaultFolder(3~6)

 メールボックス(受信、送信、削除済み)の情報を取得するにはFolder No.を3~6で指定します。今回サンプルとして受信トレイ(6)を使用します。
 本コードのポイントは下記のとおりです。

【メール用メソッドのポイント】
●フォルダ名は辞書で管理しておくと便利
●メール情報は"items.Items(<メール番号>)"で取得できる。
●最古メールのメール番号=1(※0ではない)、最新のメール番号=メール数
●メール数はCountメソッドで取得可能:Countメソッドを利用して最新メールを取得可能
●全メールを取得したいのであればFor文で処理

  下記コードで受信トレイのインスタンス作成後のメール数を確認しました。結果として受信トレイには7個のメールが残っています。

[IN]
import os
import glob
import win32com.client as win32
import pandas as pd
import datetime

# GetDefaultFolderメソッド:Outlook既定のフォルダを取得| '3:削除済みフォルダ、5:送信済みフォルダ、:6:受信トレイ
foldernames = {'Deleted': 3, #削除済みアイテム
              'SentItems': 5, #送信済みアイテム
              'Inbox': 6, #受信トレイ
              'Calender': 9, #カレンダー
              } 

idx2foldername = {v:k for k,v in foldernames.items()}

outlook = win32.Dispatch("Outlook.Application").GetNamespace("MAPI") 
num_Folder = foldernames['Inbox'] #メールボックスを指定
folder = outlook.GetDefaultFolder(num_Folder) #指定のフォルダを取得
items = folder.Items #Itemsプロパティ:フォルダ内のアイテムを取得
print(f'メール数({idx2foldername[num_Folder]}):{items.Count}') #フォルダ内のメール数

[OUT]
メール数(Inbox):7

 4-2-1.メールの各項目を取得(属性)

 メールの情報抽出は"items(<mail番号>).属性"で取得できます。各項目の属性は下記のとおりです。

【メールの属性一覧】
●SentOn:メールの送信日時※pandasでdf化する時そのままの型だとエラー
●Subject:件名
●To:宛先
●CC:CC
●SenderName:送信者名
●Body:本文
●Attachments.Count:添付ファイル数
●Attachments.Item(<添付ファイル番号>).FIlename:添付ファイル名

【補足】
●Attachments.Itemなどで添付ファイルの保存もできそう

 参考まで最新メール情報を取得します。"items.Count"を"1"に変更すれば最も古いメールの取得も可能です。

[IN]
sentOn = items(items.Count).SentOn #SentOnプロパティ:メールの送信日時を取得
subject = items(items.Count).Subject #Subjectプロパティ:メールの件名を取得
sentTo = items(items.Count).To #SentToプロパティ:メールの送信先を取得
sentCC = items(items.Count).CC #SentCCプロパティ:メールの送信先CCを取得
sendername = items(items.Count).SenderName #SenderNameプロパティ:メールの送信者名を取得
mailbocy = items(items.Count).Body #Bodyプロパティ:メールの本文を取得
num_attachment = items(items.Count).Attachments.Count #Attachmentsプロパティ:メールの添付ファイルを取得
names_attachment = [] 
for i in range(num_attachment):
    names_attachment.append(items(items.Count).Attachments.Item(i+1).FileName) #FileNameプロパティ:添付ファイルの名前を取得


_name = ['sentOn', 'subject', 'sentTo', 'sentCC', 'sendername', 'mailbody', 'num_attachment', 'names_attachment']
_data = [sentOn.strftime('%Y-%m-%d %H:%M:%S'), subject, sentTo, sentCC, sendername, mailbocy, num_attachment, names_attachment]


[OUT]
sentOn:2022-10-26 15:01:37
subject:【連絡】 設置
sentTo:KIYO
sentCC:KIYO2
sendername:note
mailbody:
    本文を記載


num_attachment:10
names_attachment:['image001.jpg', 'image002.png']

 関数化すると下記のとおりです。

[IN]
def get_mailbox(items, mailnum=items.Count):

    sentOn = items(mailnum).SentOn #SentOnプロパティ:メールの送信日時を取得
    subject = items(mailnum).Subject #Subjectプロパティ:メールの件名を取得
    sentTo = items(mailnum).To #SentToプロパティ:メールの送信先を取得
    sentCC = items(mailnum).CC #SentCCプロパティ:メールの送信先CCを取得
    sendername = items(mailnum).SenderName #SenderNameプロパティ:メールの送信者名を取得
    mailbocy = items(mailnum).Body #Bodyプロパティ:メールの本文を取得
    num_attachment = items(mailnum).Attachments.Count #Attachmentsプロパティ:メールの添付ファイルを取得
    names_attachment = [] 
    for i in range(num_attachment):
        names_attachment.append(items(mailnum).Attachments.Item(i+1).FileName) #FileNameプロパティ:添付ファイルの名前を取得
    
    return sentOn.strftime('%Y-%m-%d %H:%M:%S'), subject, sentTo, sentCC, sendername, mailbocy, num_attachment, names_attachment


sentOn, subject, sentTo, sentCC, sendername, mailbocy, num_attachment, names_attachment = get_mailbox(items, mailnum=items.Count)      
for k, v in {'sentOn':sentOn, 'subject':subject, 'sentTo':sentTo, 'sentCC':sentCC, 'sendername':sendername, 'mailbocy':mailbocy, 'num_attachment':num_attachment, 'names_attachment':names_attachment}.items():
    print(f'{k}: {v}')

[OUT]
同上

 4-2-2.全コード:全メール取得×DataFrame化

 上記のデータ取得を応用してFor文で全メール取得後にPandasでdf化しました。現状では一番下に最新メールが来るため気にならソート可能です。

[IN]
import os
import glob
import win32com.client as win32
import pandas as pd
import datetime

# GetDefaultFolderメソッド:Outlook既定のフォルダを取得| '3:削除済みフォルダ、5:送信済みフォルダ、:6:受信トレイ
foldernames = {'Deleted': 3, #削除済みアイテム
              'SentItems': 5, #送信済みアイテム
              'Inbox': 6, #受信トレイ
              'Calender': 9, #カレンダー
              } 

idx2foldername = {v:k for k,v in foldernames.items()}

outlook = win32.Dispatch("Outlook.Application").GetNamespace("MAPI") 
num_Folder = foldernames['Inbox'] #メールボックスを指定
folder = outlook.GetDefaultFolder(num_Folder) #指定のフォルダを取得
items = folder.Items #Itemsプロパティ:フォルダ内のアイテムを取得
print(f'メール数({idx2foldername[num_Folder]}):{items.Count}') #フォルダ内のメール数

def get_mailbox(items, mailnum=items.Count):

    sentOn = items(mailnum).SentOn #SentOnプロパティ:メールの送信日時を取得
    subject = items(mailnum).Subject #Subjectプロパティ:メールの件名を取得
    sentTo = items(mailnum).To #SentToプロパティ:メールの送信先を取得
    sentCC = items(mailnum).CC #SentCCプロパティ:メールの送信先CCを取得
    sendername = items(mailnum).SenderName #SenderNameプロパティ:メールの送信者名を取得
    mailbocy = items(mailnum).Body #Bodyプロパティ:メールの本文を取得
    num_attachment = items(mailnum).Attachments.Count #Attachmentsプロパティ:メールの添付ファイルを取得
    names_attachment = [] 
    for i in range(num_attachment):
        names_attachment.append(items(mailnum).Attachments.Item(i+1).FileName) #FileNameプロパティ:添付ファイルの名前を取得
    
    return sentOn.strftime('%Y-%m-%d %H:%M:%S'), subject, sentTo, sentCC, sendername, mailbocy, num_attachment, names_attachment


def get_mailbox_df(items):
    columns=['sentOn', 'subject', 'sentTo', 'sentCC', 'sendername', 'mailbocy', 'num_attachment', 'names_attachment']
    
    for i in range(items.Count):
        datas = get_mailbox(items, i+1)
        if i == 0:
            df = pd.DataFrame([datas], columns=columns)
        else:
            df = df.append(pd.DataFrame([datas], columns=['sentOn', 'subject', 'sentTo', 'sentCC', 'sendername', 'mailbocy', 'num_attachment', 'names_attachment']))
    return df

get_mailbox_df(items).reset_index(drop=True)

[OUT]

5.File System Object(FSO)

 VBAでも使用したFSOを紹介します。

5-1.ファイル情報の簡易取得

 FSOを使用してファイル情報を取得します。参考ファイルとして"sample.pdf"を使用しました。

[IN]
import win32com.client as win32
fso = win32.Dispatch("Scripting.FileSystemObject") #FSO

filepath = 'sample.pdf' #参考用PDF

print('fso.GetBaseName(filepath):', fso.GetBaseName(filepath)) #ファイル名
print('fso.GetExtensionName(filepath):', fso.GetExtensionName(filepath))
print('fso.GetFile(filepath).DateCreated:', fso.GetFile(filepath).DateCreated)
print('fso.GetFile(filepath).DateLastAccessed:', fso.GetFile(filepath).DateLastAccessed)
print('fso.GetFile(filepath).DateLastModified:', fso.GetFile(filepath).DateLastModified)
print('fso.GetFile(filepath).Drive:', fso.GetFile(filepath).Drive)
print('fso.GetFile(filepath).ParentFolder:', fso.GetFile(filepath).ParentFolder)
print('fso.GetFile(filepath).Size:', fso.GetFile(filepath).Size)
print('fso.GetFile(filepath).Type:', fso.GetFile(filepath).Type)

[OUT]
fso.GetBaseName(filepath): sample
fso.GetExtensionName(filepath): pdf
fso.GetFile(filepath).DateCreated: 2022-10-27 08:43:34+00:00
fso.GetFile(filepath).DateLastAccessed: 2022-10-27 14:13:08+00:00
fso.GetFile(filepath).DateLastModified: 2022-10-27 08:43:34+00:00
fso.GetFile(filepath).Drive: c:
fso.GetFile(filepath).ParentFolder: C:\Users\KIYO\Desktop
fso.GetFile(filepath).Size: 106176
fso.GetFile(filepath).Type: Adobe Acrobat Document

5-2.ファイル情報の詳細:fso.GetFile()

 fso.GetFile(path)でファイルオブジェクトを作成したうえで、各種プロパティを使用することで詳細情報の取得が可能です。

[IN]
filedata = fso.GetFile(filepath) #ファイル情報を取得

print('filedata.Attributes:' ,filedata.Attributes) #'ファイル属性
print('filedata.DateCreated:' ,filedata.DateCreated) #'ファイル作成日
print('filedata.DateLastAccessed:' ,filedata.DateLastAccessed) #'ファイルの最終アクセス日
print('filedata.DateLastModified:' ,filedata.DateLastModified) #'最終更新日
print('filedata.Drive:' ,filedata.Drive) #'ドライブのドライブ文字
print('filedata.Name:' ,filedata.Name) #'ファイル名
print('filedata.ParentFolder:' ,filedata.ParentFolder) #'親フォルダ
print('filedata.Path:' ,filedata.Path) #'ファイルパス
print('filedata.ShortName:' ,filedata.ShortName) #'ファイルの短い名前(8.3形式)
print('filedata.ShortPath:' ,filedata.ShortPath) #'ファイルの短いパス(8.3形式)
print('filedata.Size:' ,filedata.Size) #'ファイルサイズ [byte]
print('filedata.Type:' ,filedata.Type) #'ファイルの種類


[OUT]
filedata.Attributes: 32
filedata.DateCreated: 2022-10-27 08:43:34+00:00
filedata.DateLastAccessed: 2022-10-27 14:13:08+00:00
filedata.DateLastModified: 2022-10-27 08:43:34+00:00
filedata.Drive: c:
filedata.Name: sample.pdf
filedata.ParentFolder: C:\Users\KIYO\Desktop
filedata.Path: C:\Users\KIYO\Desktop\sample.pdf
filedata.ShortName: sample.pdf
filedata.ShortPath: C:\Users\KIYO\Desktop\sample.pdf
filedata.Size: 106176
filedata.Type: Adobe Acrobat Document

5-3.ファイルのコピー:fso.CopyFile()

 ファイルをコピーする場合はfso.CopyFile(<コピー元>,<コピー先>, bool)メソッドを使用します。

[IN]
fso.CopyFile(filepath, "sample2.pdf", True) #ファイル名を指定してコピー

[OUT]
sample2.pdf ファイルが作成(コピー)される。

参考資料

あとがき

 いろいろな操作はできると思いますがとりあえず公開します。
とりあえずさっさと「週報作成×Outlookカレンダー」を掛け合わせて「予定表から週報作ってみた」を作りたい。


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