pywinautoでRPAっぽいことをした備忘メモ

pythonのpywinautoを使った備忘メモ pythonの話

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()    #プログラム終了

コメント

タイトルとURLをコピーしました