プログラムで生活をHACKする

YRen-LaB

Python pywinauto

pywinautoでWindowsGUIからファイルアップロード

投稿日:


記事の内容

メルカリの自動出品ツールを作る際にハマったpywinautoについての記事
主にファイルアップロードダイアログを開く

ディレクトリ移動

ファイル選択

開くを押下
という流れに沿って記載していく

pywinautoとは

pywinautoは、Microsoft Windows GUIを自動化するためのPythonモジュールのセット。
マウスとキーボードのアクションをウィンドウのダイアログ等に送信できる。

他ライブラリとの差別化

公式では以下のように記載がある

Most other tools are not object oriented you end up writing stuff like:
他のほとんどのツールはオブジェクト指向ではなく、次のようなものを書くことになります

window = findwindow(title = "Untitled - Notepad", class = "Notepad")
SendKeys(window, "%OF") # Format -> Font
fontdialog = findwindow("title = "Font")
buttonClick(fontdialog, "OK")

I was hoping to create something more userfriendly (and pythonic). For example the translation of above would be:
私はもっとユーザーフレンドリーな(そしてpythonic)何かを作りたいと思っていました。たとえば、上記のコードは次のようになります。

win = app.UntitledNotepad
win.menu_select("Format->Font")
app.Font.OK.click()

また、開発者からは

ほとんどの自動化ツールはコントロールの座標またはテキストに基づいており、これらはローカライズされたソフトウェアで変更される可能性があります。そのため、(まだ実装されていませんが)私の目標は、元のソース言語(多くの場合英語)と翻訳済みソフトウェア(日本語、ドイツ語など)の間でスクリプトを変更せずに実行できるようにすることです。

とあり、実際同様のライブラリである「pyautogui」はマウスの座標で制御することが多い。

実装

import pywinauto
from pywinauto.keyboard import SendKeys

    # アップロード部分をクリックする
    browser.find_element_by_xpath("//div[@class=\"sc-gGCbJM kUoVio\"]").click()

    # 開くダイアログを探して接続する
    # ダイアログタイトルを手掛かりにwindowを探す
    findWindow = lambda: pywinauto.findwindows.find_windows(title=u'開く')[0]

    # 上記Wndowを探す処理が完了したかチェックする
    # pywinauto.timings.wait_until(タイムアウトまでの時間, 繰り返す間隔(Sec), 組み込み関数)
    dialog = pywinauto.timings.wait_until_passes(5, 1, findWindow)

    # pywinauto に探し出したダイアログを接続
    pwa_app = pywinauto.Application()
    pwa_app.connect(handle=dialog)
    window = pwa_app[u"開く"]

    addres = window.children()[39]
    addres.click()

    dialog_dir = window.children()[43]
    dialog_dir.type_keys(photo_folder+'{ENTER}',with_spaces=True)

    # テキストボックス(ファイル名)にPATHを入力
    tb = window[u"ファイル名(&N):"]
    if tb.is_enabled():
        tb.click()
        edit = window.Edit4
        edit.set_focus()
        # ファイルを選択し、Alt + Oを押下
        edit.type_keys(file_path_str + '%O',with_spaces=True)

解説

①アップロードダイアログを開く

    # アップロード部分をクリックする
    browser.find_element_by_xpath("//div[@class=\"sc-gGCbJM kUoVio\"]").click()

ここはpywinautoとは関係ない場所なので省く。
上記の要素をクリックすると、Windowsのアップロードダイアログが開く。
↓↓↓↓この記事でいうアップロードダイアログというのはこのダイアログのこと。

②ダイアログの探索

    # 開くダイアログを探す
    # ダイアログタイトルを手掛かりにwindowを探す
    findWindow = lambda: pywinauto.findwindows.find_windows(title=u'開く')[0]

自動操作するwindowsの画面を指定する
"title"にはダイアログの名前を指定

③ダイアログが見つかったか

    # 上記Wndowを探す処理が完了したかチェックする
    # pywinauto.timings.wait_until(タイムアウトまでの時間, 繰り返す間隔(Sec), 組み込み関数)
    dialog = pywinauto.timings.wait_until_passes(5, 1, findWindow)

②で指定したダイアログが見つかるか、タイムアウトになるまで待機する。
"dialog"には組み込み関数の結果が格納される。
この場合だとダイアログのIDが入る。

④ダイアログに接続

    # pywinauto に探し出したダイアログを接続
    pwa_app = pywinauto.Application()
    pwa_app.connect(handle=dialog)
    window = pwa_app[u"開く"]

IDを元にpwa_app.connectで接続する

⑤ダイアログのディレクトを選択状態にする

    addres = window.children()[39]
    addres.click()

children()でダイアログの要素を取得できる。
ディレクトリ部分は[39]だったためそこを指定し、Click
これで下図のようなディレクトリ部を選択した状態になる。

⑥移動先のディレクトリ文字列を入力

    dialog_dir = window.children()[43]
    dialog_dir.type_keys(photo_folder+'{ENTER}',with_spaces=True)

ここでtype_keysを使用しディレクトリ部に移動したい"ディレクトリの文字列 + ENTER"キーを送る。
仕様か私のコーディングの問題かは不明だが
[39]の要素に対してtype_keysを使用してもエラーのにもならなければ動作もしなかった。

再度デバッグで要素を確認すると要素[39]をクリック後
要素[43]に移動する前のディレクトリの文字列が入っていた。

そのため、試しに[43]に移動先のディレクトリ文字列を入れると
想定通りに動作したためこうなっている。

⑥ファイル名に指定ディレクトリ以下のファイル名を入れて押下

    # テキストボックス(ファイル名)にPATHを入力
    tb = window[u"ファイル名(&N):"]
    if tb.is_enabled():
        tb.click()
        edit = window.Edit4
        edit.set_focus()
        # ファイルを選択し、Alt + Oを押下
        edit.type_keys(file_path_str + '%O',with_spaces=True)

ファイル名部分をクリックし、フォーカスを設定後
"ファイル名 + Alt+Oを表す'%O'を送る"

動作結果

まとめ

以上、メルカリ自動化ツールで使っているpywinautoのまとめでした。
pyautoguiと比べると座標を覚えたりする必要ないので
個人的にはこっちの方が好きです。

日本語ドキュメントが少ないこともあり
先人様のコピペや試行錯誤した結果不要な部分もあるかと思いますが
一応動いたので、まぁ良しとしました。
もし何かあればコメント等でご教授いただければと思います。

-Python, pywinauto
-,

Copyright© YRen-LaB , 2024 AllRights Reserved Powered by AFFINGER4.