背景
PythonでRPAを行うためのライブラリの一つに PyAutoGUI があります。
Windowsアプリを操作する方法としては、pywinauto もあります。
pywinauto は画面上のUI要素を取得して、
- ボタン
- テキストボックス
- コンボボックス
- ラジオボタン
などを直接指定して操作できます。
一方で、実際に操作したいUI要素を探すために、
print_control_identifiers()
などを使って画面のUI構造を調べる必要があります。
この作業が意外と手間でした。
そこで今回は、より単純に画面を操作できる PyAutoGUI を使ってみます。
PyAutoGUIでは、
- マウスを指定した座標へ移動する
- 指定した座標をクリックする
- スクリーンショットを取得する
- 保存した画像と一致する場所を画面から探す
といった操作ができます。
ただし、PyAutoGUIには、pywinautoのように指定したUI要素の状態を待つための専用のwait()メソッドはありません。
例えば、pywinautoでは、
root_win["設定"].wait("visible", timeout=10)
のように、指定したUI要素が表示されるまで待つことができます。
そこで今回はPyAutoGUIを使って、
「保存しておいた画像と同じ画面が表示されたら、次の処理へ進む」
という処理を自分で実装してみます。
目的
今回は、画面操作後に画面が切り替わったかを画像認識で判断するプログラムを作成します。
例えば、
「ボタンをクリックした後、次の画面が表示されるまで待つ」
という処理です。
固定時間だけ待つのではなく、目的の画面が表示されたことを確認してから次の処理へ進むことを目指します。
想定する動作
今回はWindowsのスタートメニューからメモ帳を起動する処理を例にします。
- Windowsのアイコンボタンをクリック
- メモ帳のアイコンをクリック
- メモ帳の画面が表示されるまで待つ
- メモ帳の画面が表示されたことを確認したら
open notepadと表示 - 終了
イメージとしては、
Windowsボタン
↓
メモ帳をクリック
↓
待つ
↓
メモ帳の画面が表示された?
↓
YES
↓
open notepadと表示
という流れです。
画面認識の方法
メモ帳が起動したときの画面をあらかじめスクリーンショットとして保存しておきます。
今回は、
open_notepad.png
というファイル名にします。
プログラムでは、
pag.locateOnScreen()
を使って、現在の画面から open_notepad.png と一致する画像を探します。
画像が見つかった場合は、
「メモ帳の画面が表示された」
と判断して次の処理へ進みます。
プログラム
import pyautogui as pag
import time
def wait_for_image(image, timeout=10, confidence=0.8):
"""指定した画像が表示されるまで待つ"""
start = time.time()
while time.time() - start < timeout:
try:
location = pag.locateOnScreen(
image,
confidence=confidence
)
if location:
return location
except pag.ImageNotFoundException:
pass
time.sleep(0.2)
return None
def click_and_wait(
x,
y,
next_image,
timeout=10,
confidence=0.8
):
"""クリック後、指定画像が表示されるまで待つ"""
pag.click(x, y)
location = wait_for_image(
next_image,
timeout=timeout,
confidence=confidence
)
if location:
print(f"{next_image} を確認しました")
return True
print(f"{next_image} が表示されませんでした")
return False
loc = pag.locateCenterOnScreen("windows_button.png")
pag.click(loc)
time.sleep(2)
if click_and_wait(1542, 1484, "open_notepad.png"):
print("open notepad")
else:
print("can not open notepad")
time.sleep(1)
wait_for_image()
今回の処理で重要なのが、この関数です。
def wait_for_image(image, timeout=10, confidence=0.8):
指定した画像が画面に表示されるまで待ちます。
例えば、
wait_for_image("open_notepad.png")
とすると、open_notepad.png が画面に表示されるまで待ちます。
タイムアウト
いつまでも画像が見つからないとプログラムが終了しないため、タイムアウトを設定しています。
timeout=10
の場合、最大10秒間待ちます。
画像が見つかった
↓
すぐに次の処理へ
画像が見つからない
↓
最大10秒待つ
↓
見つからなければNoneを返す
0.2秒ごとに確認
ループの最後に、
time.sleep(0.2)
を入れています。
そのため、約0.2秒間隔で画面を確認します。
例えば、
0.0秒 → 画像なし
0.2秒 → 画像なし
0.4秒 → 画像なし
0.6秒 → 画像発見
↓
次へ進む
となります。
固定で10秒待つのではなく、画面が表示された時点で次の処理へ進めるのがポイントです。
click_and_wait()
もう一つの関数が、
def click_and_wait(...):
です。
これは、
「クリックする → 指定した画像が表示されるまで待つ」
という処理をまとめたものです。
例えば、
click_and_wait(
1542,
1484,
"open_notepad.png"
)
とすると、
-
(1542, 1484)をクリック -
open_notepad.pngが表示されるまで待つ - 表示されたら
True - タイムアウトしたら
False
を返します。
そのため、
if click_and_wait(...):
print("open notepad")
else:
print("can not open notepad")
のように、画面遷移の成否を判定できます。
画像認識で注意すること
PyAutoGUIの画像認識では、スクリーンショットの見た目と画面が一致していることが重要です。
特にWindowsでは、**ディスプレイの拡大縮小率(スケーリング)**に注意が必要です。
自分の環境では200%にしている状態では認識に失敗しました。そのため100%状態にしてスクリーンショットを撮影し、pythonの動作時も100%にしています。
今回のポイント
今回作成した処理では、
pag.click()
で画面を操作した後、
pag.locateOnScreen()
で目的の画面が表示されたかを確認しています。
つまり、
操作する
↓
画面が切り替わる
↓
画像を探す
↓
画像が見つかる
↓
次の処理へ
という流れです。
単純に、
time.sleep(10)
として10秒待つよりも、
「次の画面が表示されたことを確認してから進む」
という処理にしたほうが、RPAでは扱いやすくなります。
まとめ
PyAutoGUIを使うと、UI要素を細かく調査しなくても、
- 座標を指定してクリックする
- 画像を探す
- 画像が表示されるまで待つ
といった処理を組み合わせて、比較的簡単にRPAを作ることができます。
特に今回のような、
「操作後に目的の画面が表示されたことを確認してから次の処理へ進む」
という処理は、RPAを安定させるうえで重要だと思います。