pythonを使ってwindowsのアプリを操作してRPAっぽいことができるらしいので、pywinaotoを使ってみました。
ライブラリのインストール
とりあえず、使ったのはpywinautoとpygetwindow。pygetwindowはpywinautoとpyautoguiを入れたら使えるらしいので、2つをインストール。
pip install pywinauto
pip install pyautogui
とりあえずインポートしとくもの
pywinautoとpygetwindowをimportしておく。
from pywinauto import Application #アプリを開くとき
from pywinauto import Desktop #すでにアプリが開いているとき
import pygetwindow as gw
import time
import sys
アプリケーションを立ち上げる
アプリを立ち上げて、パスワードを入力し、ログインボタンを押す。
exe_path = C:\xxxxxxx\yyyyyy\xxxxxx.exe
app = Application(backend="uia").start(exe_path)
gamen_title = "XXX-ログイン"
root_win = app[gamen_title]
root_win.type_keys("PASSWORD")
ok_button = root_win.child_window(title="ログイン", control_type="Button")
ok_button.click()
立ち上がっているアプリを捕まえる
立ち上がっているアプリをroot_winに格納する。
app = Desktop(backend="uia")
# 開いているウインドウのタイトルをすべて出力
print(gw.getAllTitles())
root_win = app["メイン画面のタイトルなど"]
app[“メイン画面のタイトルなど”]では、カッコ内は、後述のprint_control_identifiers()で出力される各要素の2行目のテキストのリストの中のものが指定の対象になる。
配下の要素をツリーで出力する
そのアプリの大元のウインドウで、「root_win.print_control_identifiers()」を実行する。
root_win.print_control_identifiers()
#filenameのパラメータを設定するとテキストファイルに出力できる
root_win.print_control_identifiers(filename="treedata.txt")
画面や要素が多いと時間がかかるので気長に待つ。
各要素は以下のように3行づつで出力される。このなかで、3行目のchild_windowを使えばその要素が簡単に取得できる。
Button - 'キャンセル' (Lxxxx, Txxx, Rxxx, Bxxx)
['キャンセルButton', 'キャンセル', 'Button', 'Button0', 'Button1']
child_window(title="キャンセル", auto_id="btnCancel", control_type="Button")
文字エラーが出た時は回避方法がある。⇒リンク
要素を取得する。
child_windowで取得できる。
ok_button = root_win.child_window(title="ログイン", control_type="Button")
要素が多いと時間がかかるときがある。
mainwin.print_control_identifiers()で見れる2行目のテキストのリスト内のテキストを使っても指定可能。
app["メイン画面のタイトルなど"]["要素のテキストなど"]
そんなときはツリー情報で1階層づつ辿って指定するほうが早いような気がする。
自作で関数を作ってみた。部分一致で要素のテキストでヒットさせて、ヒットしたものをすべてリストにいれて返す。なので、要素として使うときは、要素を[0]などで取り出す必要がある。
def catch_child_elms(p_elm, target_str)
child_elms = []
for child_elm in p_elm.children():
try:
if target_str in child_elm.texts()[0]:
child_elms.append(child_elm)
except:
pass
return child_elms
再帰バージョンでも作ってみた。対象の階層を指定できる。
配下の要素が深かったり多かったりして時間がかかるときは「max_deepth」を数階層にすれば早く探せるはず。
# 再帰的に要素を探す関数
def saiki_catch_elms(p_elm, target_str, max_deepth=99):
hit_elms = []
def serch_children(elm, deepth):
for child_elm in elm.children():
try:
child_text = child_elm.texts()[0]
except:
child_text = "dummy"
if target_str in child_text:
hit_elms.append(child_elm)
if deepth < max_deepth:
if len(child_elm.children()) > 0:
serch_children(child_elm, deepth+1)
serch_children(p_elm, 1)
return hit_elms
elms = saiki_catch_elms(elm1, "XXXXX", max_deepth=3)
print(elms)
要素の中身を見る
要素のプロパティは「.get_properties()」で取得できる。
pywinautoの要素では、friendly_class_name、texts、あたりが検索などで使えるような気がする。
ok_button = root_win.child_window(title="ログイン", control_type="Button")
#プロパティを見る
ok_button.get_properties()
>>
{'class_name': '', 'friendly_class_name': 'Button', 'texts': ['XXXX'],
'control_id': None, 'rectangle': <RECT Lxxxx, Txxxx, Rxxxx, Bxxxx>,
'is_visible': True, 'is_enabled': True, 'control_count': 0,
'is_keyboard_focusable': False, 'has_keyboard_focus': False,
'automation_id': ''}
キーやテキストの送信やクリック
キー操作やテキストの入力はtype_keys()。ボタン要素はclick()でクリック操作できる。
elm.type_keys("TEXT")
#特殊キー alt:%, ctrl:^, alt+wなら"%w"
#{TAB},{UP}{DOWN}{LEFT}{RIGHT},{ENTER}, {SPACE} など
#ボタンのクリック
ok_button = root_win.child_window(title="ログイン", control_type="Button")
ok_button.click()
画面をアクティブにする。
getWindowsWithTitleのactivate()でアクティブにできる。ただエラーがよく出る。
GamenTitle = "ログイン"
gw.getWindowsWithTitle(GamenTitle)[0].activate()
エラーの代替として、最小化、最大化という方法がある。ただウインドウのサイズが最大化してしまうので、もとの位置と元のサイズに戻すのも入れてみた。
最小化、最大化する前のポジションとサイズを取得しておいて、moveToとresizeToでもとのポジションとサイズに戻す。
GamenTitle = "ログイン"
tgt = gw.getWindowsWithTitle(GamenTitle)[0]
tgt_position = [tgt.left, tgt.top]
tgt_size = [tgt.width, tgt.height]
tgt.minimize()
tgt.maximize()
tgt.moveTo(*tgt_position) #(left, top)
tgt.resizeTo(*tgt_size) #(width, height)
pywinauto以外のもの
画面変遷などでの待ち時間はtime.sleep(xx)を使う。
#待ち時間をつける。カッコ内はXX秒
time.sleep(1) #1秒
time.sleep(0.5) #0.5秒
途中でメッセージボックス的なものを出すときはtkinterを使う
# 確認ウンドウ(メッセージボックス的なもの,yes、noの選択)
from tkinter import Tk,messagebox
def ConfirmMsgbox(ConfirmMessage):
root = Tk()
root.attributes('-topmost', True)
root.withdraw()
ret = messagebox.askyesno('確認', ConfirmMessage)
return ret
#使い方
msg = "●●の画面が出ているか確認してください。Noは終了します。"
ans = ConfirmMsgbox(msg)
if ans == False:
print("ユーザーの選択により終了しました。")
sys.exit() #プログラム終了
コメント