初心者が Python プログラミングを学びながら RPA の作り方を学習する欲張り連載です。
連載第一回は以下のリンクからどうぞ。PythonやRPAに必要なパッケージのインストール方法から解説しています。
今回は、ただマウスやキーボードを操作するといった、 RPA の機能の断片ではなく、それをプログラミング的に処理する第一歩として、「指定のウィンドウ(メモ帳)の中から、指定の画像を探してクリックする」RPAを作ってみたいと思います。
他の RPA ツールだと最初から備わっていることも多い機能ですが、PyAutoGUIでは、Pythonのプログラムを通して実現する必要があります。
- PyGetWindowでウィンドウのサイズを取得する方法
- PyAutoGUIで指定領域のスクリーンショットを撮影する方法
- PyAutoGUIで画像の中から画像を探す方法
- ウィンドウ座標からスクリーン座標(デスクトップ座標)に変換する方法
今回の内容から、大体どうすればいいか想像ができた人は、きっとプログラミング経験者ですね。分からなくても大丈夫、順を追って説明していきます。
PyGetWindowでウィンドウサイズを取得する。
まずは、chapter-6.py として、Python ファイルを作成しましょう。今回は、対話モードで進めるには少し大変なので、ファイルにPython を記述していきます。
import pyautogui as ag
import pygetwindow as gw
memo = gw.getWindowsWithTitle('メモ帳')[0]
memo.activate()
x, y = memo.topleft #memo.left, memo.top
width, height = memo.size #memo.width, memo.height
import 文やWindowの取得方法は大丈夫でしょうか? 忘れてしまった方は、連載2回目を復習してくださいね。
>>Python で RPAを書いてプログラムを学ぶ その2. Window操作
今回も、メモ帳からメモ帳アイコンを探したいと思いますので、メモ帳ウィンドウを取得しています。
そして、変数のx, yに、メモ帳の左端のスクリーン座標上のX, 上端のスクリーン座標上でのY座標を保存しています。左上の位置がこれで特定できました。
次に、width, heightという変数にメモ帳の大きさ(ピクセル数)を保存しています。
memo.sizeという変数に大きさの情報が入っています。これは、X, Y 座標と同じく、値のペアとして保存されているので、アンパッキングでwidthとheightに分割しています。また、コメントに書いた通り、memo.width, memo.heightにそれぞれ分かれた数値が保存されているので、こちらを利用しても大丈夫です。
PyAutoGUIで指定領域のスクリーンショットを撮影
さて、指定ウィンドウ上から画像を探す方法ですが、大きく分けて二つのアプローチが考えられます。
- 探索対象をウィンドウの範囲にしぼってから画像を探す方法
- デスクトップ全体から画像を探して、それがメモ帳のウィンドウの領域内にあるか探す方法
探すのが先か、範囲を絞るのが先か、という問題です。
多くの場合では、広い範囲から探すよりも狭い範囲から探す方が早いですので、今回は1のアプローチをとってみます。処理方法や、画像の位置によっては順序が逆転する可能性もゼロではありませんが、通常の RPA で問題となることは少ないと思います。
ss = ag.screenshot(region = (x, y, width, height)) #メモ帳のスクリーンショットを撮影して、変数ssに格納する
指定領域のスクリーンショットの撮影方法は、上記のコードとなります。丸括弧()が二重になっていて少し混乱するかもしれませんが、screenshot()関数の中に、さらにregion = (x, y, width, height)のグループが入っています。
regionというのは、screenshot関数にとって、必須ではないオプションであるため、「今回はregionを使うよ」と宣言してから、=の後ろでregionの内容を指定しています。
さらに括弧がついているのは、XY座標と、大きさ(高さ、幅)を示す4つの数字をセットにしているためです。x = x, y =y, width = width, height = height….なんて入力していたら面倒ですからね。
そして、このregionに指定した値は、さきほどメモ帳のウィンドウから取得した、メモ帳左上の座標と、メモ帳ウィンドウの大きさそのものです。
こうすることで、PyAutoGUIでメモ帳の領域だけのスクリーンショットを撮影できます。
前回のように、ファイル名を含むパスを指定することでファイルに保存することもできますが、今回は画像検索に用いるだけですので、ファイルには保存せずに変数に代入するだけにしています。
次は、この画像の中からメモ帳のアイコンを探してみます。
PyAutoGUIで画像の中から指定画像を探す方法
position = ag.locate(r'ScreenShot\memoicon.png', ss, grayscale=False)
print(position)
Box(left=25, top=6, width=36, height=44)
前回から続けてやっている方は、前回のメモ帳アイコンのスクリーンショットを利用しましょう。今回から見ている方は、Snipping Toolなどで、メモ帳左上のアイコンを保存して、Pythonファイルの保存してあるフォルダにScreenShotというフォルダを作って、その中に格納してください。
画像の中から画像を探す関数はlocate()関数です。画面全体から探す場合のlocateOnScreen関数では、探す画像1つを引数に渡せばよかったですが、locate関数では、locate(探したい画像, 探したい画像が含まれているはずの画像)の順で指定します。
今回は、探したい画像はファイルから、探したい画像が含まれている画像は変数(メモリ)から指定していますが、両方ファイルでも、両方変数でも動作します。
そして、ここまでのプログラムを一度動作させて、position変数をprint関数で表示すると、上記のような結果になります。
ここで、ウィンドウの位置をあれこれ動かしてみましょう。動かしてみても、positionの数値は探したい画像を変えない限り一定のはずです。
なぜなら、メモ帳のアイコンは、ウィンドウの左上から常に一定の位置にあるからです。
このアイコンをクリックするには、変わるメモ帳ウィンドウの位置と、メモ帳内では変わらないメモ帳アイコンの位置をあわせないといけません。
ウィンドウ座標からスクリーン座標に変換する
では早速、メモ帳アイコンの位置をデスクトップ全体の座標、スクリーン座標に変換していきます。
local_x, local_y = ag.center(position) #画像の中での、メモ帳アイコンの中心を求める
click_x = x + local_x #メモ帳のスクリーン上での左端のX座標を足して、アイコンの中心のX座標をスクリーン座標に変換する
click_y = y + local_y #メモ帳のスクリーン上での上端のY座標を足して、アイコンの中心のY座標をスクリーン座標に変換する
ag.click(click_x, click_y) #メモ帳のアイコンをクリックする
まずは、アイコンのど真ん中をクリックしたいので、center関数でメモ帳アイコンの中心の座標を求めます。今はメモ帳のウィンドウ座標(もっと正確にいえば、メモ帳のスクリーンショットの座標)を扱っていますので、center()関数の結果もウィンドウ座標です。
さて、ここでメモ帳のウィンドウ座標・画像の座標とは何か? と考えてみましょう。これはメモ帳の左上端から、右にX, 下にYだけずれた距離と言い換えることができますね。
つまり、メモ帳の左上端のスクリーン座標から、右方向にウィンドウ座標のX分、下方向にウィンドウ座標のY分移動した点が求めたいスクリーン座標となります。
そうと分かれば簡単です。メモ帳の左上のX, Y座標に、メモ帳アイコンの中心のX, Yを足せば、目指すクリックしたいスクリーン座標となります。
また、ただ、x + local_xとしただけだと、計算をするだけして保存されません。再利用する場合には、=演算子を使って別の変数に代入するのを忘れないようにしましょう(再利用しない場合は、click(x + local_x, y + local_y)のようにして計算結果だけをclickに渡すこともできます)
この点をclick関数でクリックさせると、ウィンドウを動かしても問題なくメモ帳アイコンがクリックされます。
また、大体の環境で前回よりも動作が高速化したことが分かると思います。
おわりに
今回は RPA の学習という意味では少し応用的、プログラミングの学習という点で言えば基本の「計算」を行ってみました。計算と言っても、蓋をあけてみれば足し算(と、center関数による割り算)なのですが、プログラミングになれていない間は、「頭の中で考えていることがうまく出力できない!」ということがよくあります。
そういう場合には、紙でいいので、頭に考えていることを図に書き出してみると頭の中がすっきりすることがよくありますよ。
- ウィンドウ座標をスクリーン座標に変換するには、ウィンドウ左上のスクリーン座標にウィンドウ座標を足す
- 大体の場合、先に探す範囲を絞った方が処理が早く終わる
- 計算結果は、別の変数に保存しないと使い捨てになる