Pythonでブラウザを自動操作するとき、最近はPlaywrightを使うことが増えました。
Playwrightを使っていて便利だと感じるのが、単純に数秒待つのではなく、画面や要素が操作できる状態になるまで待ってから次へ進めることです。
では、Windowsのデスクトップアプリでも同じようなことはできないのでしょうか。
以前はPyAutoGUIなどを使って、
import time
time.sleep(3)Pythonのように、「とりあえず3秒待つ」という処理を書くことがありました。
もちろんこれでも動く場面はあります。
ただ、パソコンの状態によって2秒で起動することもあれば、5秒かかることもあります。
それなら、
「3秒経ったら操作する」のではなく、「操作できる状態になったら次へ進む」
ほうが安心です。
そこで今回試してみたのが、WindowsアプリをPythonから操作できる pywinauto です。
今回はWindowsのメモ帳を使って、
- メモ帳を起動する
- 操作できる状態になるまで待つ
- 本文の入力欄を調べる
- 日本語を自動入力する
- 入力できたことまで確認する
ところまで試してみます。
pywinautoとは
pywinautoは、WindowsのGUIアプリケーションをPythonから操作するためのライブラリです。
ボタンや入力欄など、WindowsアプリのUI要素を取得して操作できます。
今回特に試してみたかったのが、wait() を使った待機処理です。
例えば、
window.wait("ready", timeout=30)とすると、単純に30秒停止するわけではありません。
ウィンドウが操作可能な状態になるまで待ち、準備ができればその時点ですぐ次の処理へ進みます。
timeout=30 は、あくまで待機する上限時間です。
固定時間待つ time.sleep() とは考え方が少し違います。
今回の環境を作る
今回は学習用として、次のフォルダを作りました。
C:\dev\pywinauto-studyPython標準の仮想環境を使います。
PowerShellで次のように実行しました。
cd C:\dev
mkdir pywinauto-study
cd pywinauto-study
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install pywinauto今回はpywinauto自体を試すことが目的なので、環境構築もできるだけシンプルにしています。
まずメモ帳を起動して待ってみる

最初は、メモ帳を起動して、操作できる状態になるまで待つだけのコードを作りました。
from subprocess import Popen
from pywinauto import Desktop
def main() -> None:
Popen(["notepad.exe"])
window = Desktop(backend="uia").window(
class_name="Notepad",
active_only=True
)
window.wait("ready", timeout=30)
print("メモ帳のウィンドウが操作可能になりました。")
if __name__ == "__main__":
main()ここでポイントになるのが、
window.wait("ready", timeout=30)です。
ready は、ウィンドウが表示され、操作可能な状態になるまで待つために使っています。
30秒間ずっと停止するわけではなく、条件を満たせばすぐ次へ進みます。
最初はうまくいかなかった
最初は、起動したアプリケーションからそのままメモ帳のウィンドウを探していました。
Application(backend="uia").start("notepad.exe")ところが、
pywinauto.timings.TimeoutError: timed outとなり、wait() がタイムアウトしました。
調べてみると、この環境では、メモ帳を起動したプロセスと、実際にウィンドウを持っているプロセスを同じものとして扱えないケースがありました。
そこで、起動処理とウィンドウ検索を分けることにしました。
Popen(["notepad.exe"])でメモ帳を起動し、
Desktop(backend="uia")からデスクトップ上のウィンドウを探します。
この方法に変更すると、メモ帳のウィンドウを取得できるようになりました。
こうした「実際に試して初めて分かる違い」も、GUI自動化では重要そうです。
本文の入力欄はどうやって見つける?

pywinautoでは、いきなり操作コードを書くより、まず対象アプリのUI要素を調べるのが大切です。
今回は、メモ帳の本文入力欄をどう指定するかを調べます。
そこで使ったのが、print_control_identifiers() です。
pywinautoから見えているUI要素を一覧表示できます。
実際にメモ帳のUI構造を調べてみました。

print_control_identifiers() でメモ帳のUI要素を確認出力の中に、次のような部分がありました。
Document - 'pywinautoで自動入力しています'さらにDocument要素の情報を確認すると、
control_type='Document'
class_name='RichEditD2DPT'となっていました。
そこで本文欄を、次のように指定できます。
editor = window.child_window(
control_type="Document",
class_name="RichEditD2DPT"
)今回はこの環境のメモ帳で RichEditD2DPT になっていました。
Windowsやメモ帳のバージョンによってUI構造が異なる可能性があるため、固定値として覚えるより、print_control_identifiers() を使って実際の構造を確認するのが良さそうです。
本文入力欄も操作可能になるまで待つ

本文欄が見つかったら、ここでもすぐに操作せず、準備が整うまで待ちます。
editor.wait("ready", timeout=30)メモ帳のウィンドウだけでなく、実際に操作する本文欄についても待機しています。
今回やりたかった、
「時間を決めて待つのではなく、状態を待つ」
という考え方です。
日本語はSetValue()で入力した
最初は type_keys() を使えばよいのかなと思いました。
ただし、type_keys() はキー入力を送る方法なので、日本語の場合はIMEなどの状態にも影響されます。
今回は本文欄がUI AutomationのValueパターンに対応していたため、文字列を直接設定する方法を使いました。
value = editor.wrapper_object().iface_value
text = "pywinautoで自動入力しています"
value.SetValue(text)これで、メモ帳へ日本語を直接入力できます。

実際にメモ帳へ、
pywinautoで自動入力していますと入力されました。
キーボード入力を再現するのではなく、入力欄の値そのものを設定しているのが今回のポイントです。
入力した後も「確認できるまで待つ」
さらに今回は、「入力命令を出して終わり」にはしませんでした。
入力した内容を読み戻して、期待した文字列と一致するまで待ちます。
from pywinauto.timings import wait_until
wait_until(
10,
0.2,
lambda: value.CurrentValue == text
)このコードでは、最大10秒の間、0.2秒ごとに値を確認しています。
期待した文字列になれば、その時点ですぐ終了します。
つまり、
window.wait("ready", timeout=30)は、操作する前の待機。
そして、
wait_until(
10,
0.2,
lambda: value.CurrentValue == text
)は、操作した後の確認待機です。
この2つを組み合わせることで、「たぶん起動しただろう」 「たぶん入力できただろう」ではなく、状態を確認しながら処理を進められます。

PowerShellには、
入力確認成功: pywinautoで自動入力していますと表示されました。
完成したコード
今回の最終的なコードです。
"""メモ帳と本文欄の準備を待ち、日本語の文字列を自動入力する。"""
from subprocess import Popen
from pywinauto import Desktop
from pywinauto.timings import wait_until
def main() -> None:
# 起動用プロセスとウィンドウを持つプロセスが異なる場合にも対応する。
Popen(["notepad.exe"])
# 複数のメモ帳が開いていても、アクティブなウィンドウを対象にする。
window = Desktop(backend="uia").window(
class_name="Notepad",
active_only=True
)
# ウィンドウが操作可能になるまで待つ。
window.wait("ready", timeout=30)
# 以降は同じウィンドウを対象にする。
window = Desktop(backend="uia").window(
handle=window.wrapper_object().handle
)
# この環境のメモ帳の本文欄は Document / RichEditD2DPT。
editor = window.child_window(
control_type="Document",
class_name="RichEditD2DPT"
)
editor.wait("ready", timeout=30)
value = editor.wrapper_object().iface_value
if value.CurrentIsReadOnly:
raise RuntimeError("本文入力欄が読み取り専用です。")
if value.CurrentValue:
raise RuntimeError(
"既存の本文を保護するため、空のタブで再実行してください。"
)
# IMEを経由せずUnicode文字列を直接設定する。
text = "pywinautoで自動入力しています"
value.SetValue(text)
# 入力結果が反映されるまで確認する。
wait_until(
10,
0.2,
lambda: value.CurrentValue == text
)
print(f"入力確認成功: {value.CurrentValue}")
if __name__ == "__main__":
main()time.sleep()を使わずに操作できた
今回pywinautoを試してみて、一番面白かったのはやはり待機処理でした。
例えば、
time.sleep(3)なら、対象アプリの状態に関係なく必ず3秒待ちます。
一方、
window.wait("ready", timeout=30)なら、
操作できる状態になったら先へ進む
という書き方ができます。
さらに wait_until() を使えば、
操作した結果が期待した状態になるまで待つ
こともできます。
ブラウザ自動化でPlaywrightを使ったときにも感じましたが、自動化では単に操作するだけでなく、
「いつ操作するか」
がかなり重要です。
pywinautoはWindows業務アプリの自動化にも使えそう
今回はメモ帳というシンプルなアプリでしたが、pywinautoではWindowsアプリのボタンや入力欄などを取得して操作できます。
そのため、
- 社内のWindows業務アプリ
- 古いデスクトップソフト
- Excelとは別の専用アプリ
- ブラウザではない入力作業
など、自動化しにくかった作業にも使える可能性があります。
もちろん、対象アプリによってUI構造は違います。
今回も、最初はウィンドウをうまく取得できませんでした。
それでも、
print_control_identifiers()などを使ってUI構造を調べながら進められることが分かりました。
まとめ
今回はpywinautoを使って、Windowsのメモ帳をPythonから操作してみました。
試した内容は、
- メモ帳を起動する
wait()で操作可能になるまで待つprint_control_identifiers()でUI要素を調べる- 本文入力欄を特定する
SetValue()で日本語を入力するwait_until()で入力結果まで確認する
という流れです。
特に印象に残ったのは、固定時間待つのではなく、状態を待てること。
GUI自動化では、ここが安定性に大きく影響しそうです。
Playwrightでブラウザ自動化を試してきましたが、Windowsのデスクトップアプリではpywinautoという選択肢もかなり面白そうです。
次は、メモ帳よりもう少し実際の業務に近いWindowsアプリでも試してみたいと思います。
