pywinauto 詳細解説:Python で Windows GUI 自動化をマスターしよう!

日々のPC作業、特に定型的なWindowsアプリケーションの操作にうんざりしていませんか? 「この作業、自動化できたらなぁ…」と考えたことがある方も多いはず。そんな願いを叶えてくれるのが、Pythonライブラリの pywinauto です!

pywinauto は、Python スクリプトを使って Windows の GUI (グラフィカルユーザーインターフェース) を自動操作するための強力なツールキットです。マウスのクリックやキーボード入力、ウィンドウの制御などをプログラムから実行できるため、業務の効率化、ソフトウェアテストの自動化、RPA (Robotic Process Automation) の構築など、幅広い用途で活用されています。

この記事では、pywinauto の基本的な概念から具体的な使い方、さらには応用テクニックまで、詳細に解説していきます。この記事を読めば、あなたも pywinauto を使って Windows 操作の自動化を始められるようになるでしょう!

pywinauto とは? その特徴とメリット

pywinauto は、Microsoft Windows の GUI を自動化するために開発された Python モジュールのセットです。その主な特徴とメリットを見ていきましょう。

  • Python による制御: 読みやすく書きやすい Python 言語で GUI 操作を記述できます。プログラミング経験があれば比較的容易に習得でき、複雑なロジックも実装しやすいのが特徴です。
  • オープンソース&無料: pywinauto はオープンソースソフトウェアであり、完全に無料で利用できます。ライセンス費用を気にせず、個人利用から商用利用まで幅広く活用可能です。
  • Windows アプリケーションに特化: Windows ネイティブアプリケーションの操作に強みを持っています。特に、Win32 API や Microsoft UI Automation (UIA) を利用して、多くの種類のアプリケーションを制御できます。
  • 柔軟な要素特定方法: ウィンドウやボタン、テキストボックスなどの GUI 要素(コントロール)を、タイトル、クラス名、コントロールID など様々な方法で特定できます。これにより、多様なアプリケーションに対応可能です。
  • バックエンドの選択: 操作対象のアプリケーション技術に応じて、`win32` (Win32 API) または `uia` (MS UI Automation) という2つのバックエンドを選択できます。これにより、古いアプリケーションから比較的新しい技術(WPF, WinForms など)で作られたアプリケーションまで、幅広く対応できます。
  • シンプルな操作: アプリケーションの起動、ウィンドウの選択、コントロールへのアクション(クリック、テキスト入力など)を、直感的で簡潔なコードで記述できます。

他の RPA ツール (UiPath, Blue Prism など) と比較すると、pywinauto はコードベースであるためプログラミング知識が必要ですが、その分、より細かい制御が可能で、ライセンス費用がかからないという大きなメリットがあります。

インストールと環境設定

pywinauto を使い始めるのは非常に簡単です。まずは Python 環境に必要なパッケージをインストールしましょう。

前提条件

pywinauto を利用するには、Python がインストールされている必要があります。公式ドキュメントによると、Python 3.6 以上が推奨されています (最新版では Python 3.7 以降が必要な場合もあります)。まだ Python をインストールしていない場合は、Python 公式サイト からダウンロードしてインストールしてください。

また、開発を進める上では、プロジェクトごとに環境を分離できる「仮想環境」の利用を強く推奨します。仮想環境を使うことで、ライブラリのバージョン競合などを防ぐことができます。


# 仮想環境を作成 (例: .venv という名前の仮想環境)
python -m venv .venv

# 仮想環境をアクティベート (Windowsの場合)
.\.venv\Scripts\activate

# (Linux/macOSの場合)
# source .venv/bin/activate
            

pip を使ったインストール

Python 環境(できればアクティベートされた仮想環境)が準備できたら、pip コマンドを使って pywinauto をインストールします。


pip install -U pywinauto
            

-U オプションは、既にインストールされている場合に最新版へアップグレードするためのものです。

また、コントロールのスクリーンショットを取得する機能 (`capture_as_image()`) を利用したい場合は、画像処理ライブラリ Pillow もインストールする必要があります。


pip install Pillow
            

これで pywinauto を使用する準備が整いました!

基本的な概念

pywinauto を効果的に使うためには、いくつかの基本的な概念を理解しておくことが重要です。

バックエンド (Backend)

pywinauto が Windows アプリケーションと通信し、GUI 要素を操作するための基盤となる技術です。主に以下の2種類があります。

  • win32 (Win32 API): 古くからある Windows の標準 API です。MFC, VB6, VCL などで作られたレガシーなアプリケーションや、一部の WinForms アプリケーションの操作に適しています。デフォルトのバックエンドです。比較的軽量で高速に動作することがあります。
  • uia (Microsoft UI Automation): より新しいアクセシビリティ技術です。WinForms, WPF, Qt, ブラウザなど、よりモダンな技術で開発されたアプリケーションの操作に適しています。win32 では認識できないコントロールを認識できる場合があります。

どちらのバックエンドを使用するかは、操作対象のアプリケーションによって選択します。アプリケーションの起動時や接続時に backend="win32" または backend="uia" のように指定します。

エントリーポイント (Entry Point)

自動化操作の起点となるオブジェクトです。主に2つのクラスがあります。

  • Application: 特定のアプリケーションプロセスに接続し、そのプロセス内のウィンドウやコントロールを操作します。アプリケーションの起動 (start()) や、既に実行中のプロセスへの接続 (connect()) に使用します。プロセスを跨いだ操作は基本的に行いません。
  • Desktop: デスクトップ全体を操作対象とします。特定のプロセスに限定せず、複数のアプリケーションやプロセスにまたがるウィンドウを操作する場合に使用します。バックエンドを指定して初期化します (例: Desktop(backend="uia"))。

ウィンドウ स्पेसिफिकेशन (Window Specification)

操作したいウィンドウ(ダイアログを含む)を一意に特定するための情報です。Application オブジェクトや Desktop オブジェクトに続けて、角括弧 [] やドット . を使ってウィンドウを指定します。

ウィンドウの特定には、以下のような情報が利用できます。

  • タイトル (Window Text)
  • クラス名 (Class Name)
  • 最適な一致 (Best Match)
  • 正規表現

例: app["メモ帳"], app.UntitledNotepad, app.window(title_re=".*メモ帳")

コントロール (Control)

ウィンドウ内のボタン、テキストボックス、リストボックス、メニューなどの個々の GUI 要素です。Window Specification と同様に、角括弧やドットを使ってアクセスします。

コントロールの特定には、以下のような情報が利用できます。

  • テキスト (Title, Label)
  • クラス名 (Class Name)
  • コントロールタイプ (Control Type, `uia` バックエンドで有効)
  • 自動化 ID (Automation ID, `uia` バックエンドで有効)
  • インデックス番号

例: dlg["OK"], dlg.OKButton, dlg.Edit, dlg.child_window(auto_id="CalculatorResults", control_type="Text")

実践!pywinauto の基本的な使い方

それでは、実際に pywinauto を使って簡単な Windows アプリケーション(メモ帳)を操作してみましょう。

1. ライブラリのインポート

まず、必要な `Application` クラスをインポートします。


from pywinauto.application import Application
            

2. アプリケーションの起動

`Application` クラスの `start()` メソッドを使って、メモ帳 (notepad.exe) を起動します。ここでは `uia` バックエンドを指定してみます。


# uia バックエンドを指定してメモ帳を起動
app = Application(backend="uia").start("notepad.exe")

# デフォルトの win32 バックエンドで起動する場合
# app = Application().start("notepad.exe")
            

3. ウィンドウの特定と操作

起動したメモ帳のウィンドウを特定し、操作します。ウィンドウのタイトルは環境によって異なる場合があるため注意が必要です(例: “無題 – メモ帳”, “Untitled – Notepad”)。


# ウィンドウタイトルでウィンドウを取得 (実際のタイトルに合わせて変更)
# 日本語環境の場合: dlg = app["無題 - メモ帳"]
# 英語環境の場合: dlg = app.UntitledNotepad
dlg = app.window(title_re=".*メモ帳") # 正規表現でタイトルに「メモ帳」が含まれるウィンドウを探す

# ウィンドウが表示されるまで待機 (推奨)
dlg.wait('visible')

# メニューを選択 ("ヘルプ" -> "バージョン情報")
# 日本語環境: dlg.menu_select("ヘルプ(&H)->バージョン情報(&A)")
# 英語環境: dlg.menu_select("Help->About Notepad")
dlg.menu_select("ヘルプ(&H)->バージョン情報(&A)")

# "バージョン情報" ダイアログが表示されるのを待つ
about_dlg = app.window(title="バージョン情報") # または title="About Notepad"
about_dlg.wait('visible')

# "OK" ボタンをクリック
# ボタンのテキストで指定: about_dlg["OK"].click()
# コントロールタイプとテキストで指定: about_dlg.OKButton.click()
about_dlg["OK"].click()

# メモ帳のエディットコントロールにテキストを入力
# コントロールタイプで指定: dlg.Edit.type_keys("pywinauto は楽しい!", with_spaces=True)
# タイトルで指定 (もしあれば): dlg["テキスト エディター"].type_keys("pywinauto works!", with_spaces=True)

# 日本語入力の場合は set_edit_text の方が確実な場合がある
try:
    # まず Edit コントロールを探す
    edit_control = dlg.child_window(class_name="Edit")
    edit_control.set_edit_text("pywinauto で自動入力テスト。")
except Exception as e:
    print(f"エディットコントロールが見つからないか、テキスト設定でエラー: {e}")
    # Modern なメモ帳 (UWP) の場合、コントロール構造が異なる可能性がある
    # その場合は AutomationId や ControlType で探す必要がある
    try:
        # 例: UWP メモ帳のテキスト領域 (AutomationId は Inspect.exe などで確認)
        edit_control_uwp = dlg.child_window(auto_id="RichEditBox") # これは一例。実際のIDは異なる可能性あり
        edit_control_uwp.type_keys("pywinauto で自動入力テスト (UWP)。", with_spaces=True)
    except Exception as e_uwp:
        print(f"UWPメモ帳のエディットコントロールが見つからないか、エラー: {e_uwp}")


# アプリケーションを閉じる (変更を保存せずに閉じる)
dlg.close(wait_time=1) # 少し待機時間を設ける
            

この例では、メモ帳を起動し、メニュー操作で「バージョン情報」ダイアログを開いて閉じ、最後にエディット領域にテキストを入力して終了する、という一連の流れを自動化しています。`wait(‘visible’)` を使うことで、ウィンドウやダイアログが実際に表示されるまで待機させることができ、スクリプトの安定性が向上します。

注意: ウィンドウのタイトルやメニューのテキストは、OS の言語設定によって異なります。上記のコードは日本語環境を想定している部分がありますので、英語環境などでは適宜修正が必要です。また、Windows のバージョンやメモ帳アプリのバージョン(従来の Win32 版か、ストアアプリ版かなど)によってもコントロールの構造や名前が異なる場合があります。

自動化の精度を高めるためには、操作対象のウィンドウやコントロールを正確に特定することが不可欠です。pywinauto は豊富な特定方法を提供しています。

特定のための属性

ウィンドウやコントロールを特定するために、以下のような属性(識別子)を組み合わせて使用します。

属性名 (引数名) 説明 バックエンド
title ウィンドウのタイトルバーのテキストや、コントロールのラベルテキスト。 app.window(title="メモ帳"), dlg["OK"] win32, uia
class_name ウィンドウまたはコントロールのウィンドウクラス名。 app.window(class_name="Notepad"), dlg.child_window(class_name="Edit") win32, uia
control_type UI Automation で定義されるコントロールの種類 (Button, Edit, Text, Pane など)。 dlg.child_window(control_type="Button", title="保存") uia
auto_id UI Automation で定義される一意の識別子 (開発者が設定)。 dlg.child_window(auto_id="SaveButton") uia
control_id Win32 で定義されるコントロールの数値 ID。 dlg.child_window(control_id=1001) win32
title_re タイトルを正規表現で検索。 app.window(title_re=".*メモ帳.*") win32, uia
class_name_re クラス名を正規表現で検索。 app.window(class_name_re=".*Notepad.*") win32, uia
best_match 指定した文字列に最も近いタイトルやテキストを持つ要素を検索。 app.window(best_match="無題 メモ帳") win32, uia
visible_only 表示されている要素のみを対象とするか (デフォルト: True)。 app.window(title="非表示ウィンドウ", visible_only=False) win32, uia
enabled_only 有効な(操作可能な)要素のみを対象とするか (デフォルト: False)。 dlg.child_window(title="OK", enabled_only=True) win32, uia

これらの属性は、app.window(...)dlg.child_window(...) のように、キーワード引数として指定します。また、app["タイトル"]dlg.コントロール名 のような簡易的な記法も利用できますが、これは内部的に best_match や title/control_type などを使って検索しています。より厳密に指定したい場合は window()child_window() メソッドを使用します。

識別子の調査方法: print_control_identifiers()

特定のウィンドウやコントロールに、どのような識別子が利用可能かを確認したい場合、print_control_identifiers() メソッドが非常に役立ちます。


# メモ帳のメインウィンドウの識別子を表示
dlg = app.window(title_re=".*メモ帳")
dlg.print_control_identifiers()

# もし "ファイル" メニューがあれば、その識別子を表示
try:
    file_menu = dlg.child_window(title="ファイル", control_type="MenuItem")
    file_menu.print_control_identifiers()
except Exception as e:
    print(f"ファイルメニューが見つかりません: {e}")
            

このメソッドを実行すると、その要素にアクセスするために利用可能な様々な属性(タイトル、クラス名、自動化IDなど)とその値がコンソールに出力されます。これを見れば、スクリプトでどの識別子を使うのが最適か判断しやすくなります。

GUI インスペクションツール

コードからだけでなく、専用のツールを使って GUI 要素の情報を調査することも有効です。

  • Inspect.exe (UI Automation Verify): Microsoft が提供する UI Automation のためのインスペクションツール。Windows SDK に含まれています。コントロールの階層構造、プロパティ (AutomationId, ControlType, Name など) を詳細に確認できます。`uia` バックエンド利用時に特に役立ちます。
  • Spy++: Microsoft Visual Studio に付属するツール。Win32 API ベースのウィンドウメッセージやウィンドウ/コントロールのプロパティ (クラス名、コントロールIDなど) を調査できます。`win32` バックエンド利用時に役立ちます。
  • py_inspect.py: pywinauto に含まれる実験的なツール。コマンドラインから実行し、マウスカーソル下のコントロール情報を表示します。

これらのツールを使って、目的のコントロールがどのような属性を持っているかを事前に調べておくと、スクリプト作成がスムーズに進みます。

コントロールの操作

ウィンドウやコントロールを特定できたら、次はそれらを操作します。pywinauto は様々な操作メソッドを提供しています。

クリック操作

ボタンなどのコントロールをクリックするには、click()click_input() メソッドを使用します。


# OK ボタンをクリック (最も一般的な方法)
dlg.child_window(title="OK", control_type="Button").click()

# マウスカーソルを動かしてクリック (より人間に近い操作)
# coords=(x, y) でクリック位置のオフセットを指定可能
dlg.child_window(title="キャンセル", control_type="Button").click_input()

# ダブルクリック
dlg.child_window(title="ファイルを開く", control_type="ListItem").double_click_input()
            

click() は比較的早く、内部的なメッセージ送信でクリックを試みます。click_input() は実際にマウスカーソルを移動させてクリックするため、より確実ですが少し時間がかかることがあります。アプリケーションによってはどちらか一方しか効かない場合もあります。

テキスト入力

テキストボックス (Edit コントロールなど) に文字を入力するには、type_keys()set_edit_text() を使用します。


# ユーザー名フィールドにテキストを入力
dlg.child_window(auto_id="UserNameEdit").type_keys("my_username", with_spaces=True)

# パスワードフィールドにテキストを入力 (特殊文字や修飾キーも可能)
# {} で囲むと特殊キーを指定: {ENTER}, {TAB}, {F5}, ^ (Ctrl), + (Shift), % (Alt)
dlg.child_window(auto_id="PasswordEdit").type_keys("my^secret+pass{TAB}", with_tabs=True) # Ctrl+secret+Shift+pass を入力し Tab

# テキストを直接設定 (日本語など複雑な文字入力に有効な場合がある)
# 注意: 既存のテキストは上書きされる
dlg.child_window(class_name="Edit").set_edit_text("これは設定されるテキストです。")

# 現在のテキストを取得
current_text = dlg.child_window(class_name="Edit").window_text()
print(f"現在のテキスト: {current_text}")
            

type_keys() はキーボードからの入力をシミュレートします。with_spaces=True でスペースも入力、with_tabs=True でタブ文字も入力できます。特殊キーの組み合わせも可能です。 set_edit_text() はコントロールのテキストプロパティを直接設定します。キーボードイベントが発生しないため、アプリケーションによっては入力として認識されない場合がありますが、日本語などのマルチバイト文字を確実に入力したい場合に有効なことがあります。

その他のコントロール操作

  • チェックボックス (CheckBox):
    
    checkbox = dlg.child_window(title="同意する", control_type="CheckBox")
    checkbox.toggle() # 状態を反転
    if not checkbox.is_checked():
        checkbox.check() # チェックする
    if checkbox.is_checked():
        checkbox.uncheck() # チェックを外す
                            
  • ラジオボタン (RadioButton):
    
    radio_button = dlg.child_window(title="オプションB", control_type="RadioButton")
    radio_button.select() # 選択する
    is_selected = radio_button.is_selected()
    print(f"オプションBは選択されていますか? {is_selected}")
                            
  • コンボボックス (ComboBox) / リストボックス (ListBox):
    
    combo = dlg.child_window(auto_id="CountryComboBox")
    combo.select("Japan") # テキストで選択
    combo.select(2)     # インデックスで選択 (0始まり)
    
    listbox = dlg.child_window(class_name="ListBox")
    listbox.select("項目3")
    selected_items = listbox.selected_texts()
    print(f"選択中の項目: {selected_items}")
                            
  • メニュー (Menu / MenuItem):
    
    # メインウィンドウのメニューを選択
    dlg.menu_select("ファイル(&F)->名前を付けて保存(&A)...")
    
    # コンテキストメニューなどを操作する場合 (MenuItemコントロールとして取得)
    # (事前に右クリックなどでメニューを表示させておく必要あり)
    # app.PopupMenu.menu_select("コピー(&C)")
                            
  • ウィンドウの最大化・最小化・アクティブ化:
    
    dlg.maximize() # 最大化
    time.sleep(1)
    dlg.minimize() # 最小化
    time.sleep(1)
    dlg.restore()  # 元のサイズに戻す
    dlg.set_focus() # ウィンドウをアクティブにする (前面に出す)
                            
  • ウィンドウの移動・リサイズ:
    
    # 指定した座標 (左上隅) に移動
    dlg.move_window(x=100, y=100)
    time.sleep(1)
    # サイズも同時に変更
    dlg.move_window(x=0, y=0, width=800, height=600, repaint=True)
                            

これらは一部の例です。操作したいコントロールの種類に応じて、適切なメソッドを選択してください。コントロールがどのようなメソッドを持っているかは、print_control_identifiers() の出力や公式ドキュメントで確認できます。

応用テクニックと注意点

基本的な操作に慣れてきたら、より高度なテクニックや注意点も押さえておきましょう。

待機処理 (Wait)

GUI オートメーションでは、アプリケーションの応答速度や予期せぬダイアログの表示などにより、タイミングの問題が発生しやすいです。特定の状態になるまで待機する処理を入れることで、スクリプトの安定性を大幅に向上させることができます。


from pywinauto.timings import TimeoutError
import time

try:
    # "保存" ボタンが有効になるまで最大10秒待機
    dlg.child_window(title="保存", control_type="Button").wait('enabled', timeout=10)
    print("保存ボタンが有効になりました。")

    # "処理中..." というテキストが消えるまで最大30秒待機
    dlg.child_window(title="処理中...", control_type="Text").wait_not('visible', timeout=30)
    print("処理が完了しました。")

    # 特定のウィンドウが出現するまで待機
    app.window(title="完了通知").wait('exists visible', timeout=15)
    print("完了通知ダイアログが表示されました。")

except TimeoutError:
    print("タイムアウトエラー: 指定時間内に期待した状態になりませんでした。")
except Exception as e:
    print(f"待機中にエラーが発生しました: {e}")

# 単純な固定時間待機 (非推奨だが、簡単な調整には使える)
time.sleep(2) # 2秒待機
            

wait(wait_for, timeout, retry_interval) メソッドは非常に強力です。wait_for には待機したい状態を指定します(例: 'visible', 'enabled', 'ready', 'exists')。wait_not(...) は逆に、指定した状態でなくなるまで待機します。適切な待機処理を組み込むことが、堅牢な自動化スクリプトの鍵となります。

エラーハンドリング

自動化スクリプトは、予期せぬ状況(ウィンドウが見つからない、コントロールが無効になっているなど)に遭遇する可能性があります。try...except ブロックを使って、これらのエラーを適切に処理することが重要です。


from pywinauto.findwindows import ElementNotFoundError
from pywinauto.findbestmatch import MatchError

try:
    # ウィンドウやコントロールを探す処理
    main_window = app.window(title="存在しないウィンドウ")
    main_window.child_window(title="存在しないボタン").click()

except (ElementNotFoundError, MatchError) as e:
    print(f"エラー: ウィンドウまたはコントロールが見つかりませんでした。詳細: {e}")
    # ここでエラー回復処理やログ記録などを行う
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
    # 必要に応じてスタックトレースなども記録
finally:
    print("処理を終了します。")
    # 必要であれば、アプリケーションを閉じるなどのクリーンアップ処理
    # app.kill() # 強制終了
            

ElementNotFoundErrorMatchError は、要素が見つからなかった場合に発生する代表的な例外です。これらを捕捉し、状況に応じた処理(リトライ、スキップ、エラー通知など)を実装することで、スクリプト全体の安定性を高めることができます。

バックエンドの選択に関する注意点

前述の通り、win32uia のどちらのバックエンドを選択するかは重要です。

  • 基本的には、まずデフォルトの win32 で試してみて、うまくコントロールを認識・操作できない場合に uia を試すのが良いでしょう。
  • WPF や UWP (ストアアプリ) など、比較的新しい技術で作られたアプリケーションは uia バックエンドでないと操作できないことが多いです。
  • 一方、非常に古いアプリケーションや特定のカスタムコントロールは win32 でないとアクセスできない場合があります。
  • Inspect.exe などのツールを使って、対象アプリケーションのコントロールがどちらの技術で認識されやすいかを確認するのが確実です。Inspect.exe の左上のメニューで “UI Automation” / “MSAA” (Win32に近い) を切り替えて確認できます。
  • 1つのスクリプト内で両方のバックエンドを混在させることは推奨されません。基本的には、Application または Desktop オブジェクトを初期化する際にどちらか一方を指定します。

キーボードショートカットの活用

マウス操作だけでなく、アプリケーションが提供するキーボードショートカットを利用すると、より高速で安定した操作が可能な場合があります。type_keys() メソッドで修飾キー(Ctrl, Alt, Shift)と組み合わせたキー入力を送信できます。


# Ctrl + S で保存する場合
dlg.type_keys('^s')

# Alt + F4 でウィンドウを閉じる場合
dlg.type_keys('%{F4}')
            

ログとデバッグ

複雑な自動化スクリプトを作成する場合、何が起こっているかを把握するためにログ出力が役立ちます。Python の `logging` モジュールなどを活用しましょう。また、問題発生時のデバッグには、`print_control_identifiers()` や GUI インスペクションツールが不可欠です。ステップ実行しながら変数の状態を確認することも有効です。

まとめ

pywinauto は、Python を使って Windows GUI 操作を自動化するための非常に強力で柔軟なライブラリです。この記事では、その基本的な概念からインストール、主要な操作方法、そして応用的なテクニックや注意点について解説しました。

pywinauto を活用することで、以下のようなメリットが期待できます。

  • 単純な繰り返し作業からの解放と時間節約
  • 手作業によるミスの削減
  • GUI アプリケーションの自動テストによる品質向上
  • 既存システムを改修せずに RPA を実現

もちろん、GUI オートメーションは対象アプリケーションの UI 変更に弱いという側面もありますが、適切な待機処理やエラーハンドリング、そしてバージョン管理などを組み合わせることで、十分に実用的な自動化ソリューションを構築できます。

ぜひ、身の回りの定型的な Windows 操作を pywinauto で自動化することに挑戦してみてください。最初は簡単な操作から始め、徐々に複雑な処理にステップアップしていくのが良いでしょう。公式ドキュメントやコミュニティ (Stack Overflow など) も参考にしながら、pywinauto の可能性を探求してみてください!

Happy Automating with pywinauto!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です