本連載は、Python, プログラミング、RPAの初心者が、 RPA を作りながらPython を学習するという欲張りな企画です。 RPA で日頃の業務を楽にしたい、でも転職・複業のためにプログラミングも学習したいという方におすすめです。
今回は、PyAutoGUI と PyGetWindow で基本的な RPA を作る基本機能を一挙に紹介します。Windows 環境を想定していますが、 PyAutoGUIも PyGetWindowも(!)それ以外のOSでも動作するので、Macをお使いの方にも役立ちます。
Python の インストール、 Python モジュールのインストールなど基本的なことは、連載第一回のページを参照してください。
- PyAutoGUI の基本的な機能・関数を利用する
- PyGetWindow を組み合わせて、目的のウィンドウを操作する
- 指定の時間だけ待つ
今回の内容だけで、「日頃ちょっと面倒だな」と思っている作業の自動化くらいまではできてしまいます! プログラミングの学習は、成功体験がなかなか得られずにモチベーションの維持が難しい面がありますが、日常的に「使える」ものが作れることで、これからの学習のモチベーション維持にも役立ちますよ。
※本連載は初心者向けです。すでにPython を学習している方で、ちょっとPyAutoGUIの概要が知りたい、という場合には物足りない内容になるかもしれません。
メモ帳を使って基本的なキーボード操作を行う
では、基本の基本、メモ帳を操作します。メモ帳はできるだけ、1つだけ起動しておいてください。Python ファイルの編集には、サクラエディタなどの別のエディタを使っていることを想定します。
ここでは仮に、chapter-4.py と命名することにします。
まずはimport
今回使うモジュールをimport していきましょう。
import pyautogui as ag
import pygetwindow as gw
import time
import pyperclip as cb
timeとpyperclipという新しいモジュールが登場しました。インストールした記憶がなくても問題ありません。timeはPythonが動く環境であれば通常標準で動作するモジュールで、pyperclipについては、PyAutoGUIと一緒にインストールされているはずです。もし、インストールされていなければ、コマンドプロンプトで「py -m pip install pyperclip 」としてインストールしてください。
なお、Windows のウィンドウを操作できるパッケージ・モジュールは他にもありますが、PyAutoGUIと同時インストールされること、機能がシンプルで RPA 向きなことからこれを採用しています(使えるのであれば、他でも全然構いません)。
操作するメモ帳のウィンドウを取得
特定のウィンドウを取得するには、PyGetWindow の getWindowsWithTitle関数を使うのでした。また、ウィンドウは複数ある可能性があるので、配列で結果が帰ってくるので[0]をつけて先頭の要素(値)を取り出す必要がありました(以前の復習です)。
memo = gw.getWindowsWithTitle('メモ帳')[0]
これで、RPA のターゲットになるウィンドウを特定できました。そうしたら、memo のメソッドを使って、メモ帳をアクティブにしましょう。
memo.activate()
これで、メモ帳がアクティブ、普段私たちが操作しているときの状態となりました。
文字を入力する
メモ帳と言えば、文字の入力です。文字入力で最も簡単なのは、write()関数です。
ag.write('hello, python\n')
ag.write('こんにちは、パイソン\n')
と入力して py chapter-4.py を入力してみましょう。フォントの関係で\となっていますが、キーボード操作は¥(円記号)で入力できます。
どうでしたか?
上図のようになって、hello, python は入力されていますが、こんにちは、パイソンは入力されなかったのではないでしょうか? また、\n という文字も入力されていないと思います。
まずは\nについてです。これは、Python を含む大体のプログラムで共通のルールで「改行」を意味しています。メモ帳をよく見ると、改行が2回されているのが分かると思います。つまり、\nが2回のwrite関数入力されています。
では、「こんにちは、パイソン」は何故入力されなかったのでしょうか?
答えは、writeが、キーボードの操作をそのまま実行する RPA だからです。ためしに、メモ帳で日本語入力をオンにしてから、chapter-4.pyをもう一度実行してみてください。
使っている日本語入力ソフトによって結果は異なると思いますが、なかなか面白い入力結果になったと思います。これは、日本語入力で「hello, python(エンターキー)」と入力した結果というわけです。
キーボードには通常、日本語キーというものはないので(かな入力もローマ字を無理矢理ひらがなに割り当てているようなものです)、write関数が動作しなかったんですね。
では、日本語を入力する場合にはどうしたらいいでしょうか?
クリップボード経由で文字列を入力する
PyAutoGUIは本当に人間の操作をシミュレートするものですので、このままでは日本語入力ができません(日本語入力ソフトを経由して入力することもできますが……想像しただけで大変なのは分かると思います)。
ということで、Pythonでクリップボードを操作したいと思います。ここで登場するのが、冒頭で紹介していたpyperclipですね。今回は、as cb(クリップボードの略のつもりです)と省略してimport しています。
ということで、先ほどのPython スクリプトを以下のように修正しましょう。
ag.write('hello, python\n')
#ag.write('こんにちは、パイソン\n')
cb.copy('こんにちは、パイソン\n')
ag.hotkey('ctrl', 'v')
ag.write(‘こんにちは、パイソン\n’)の先頭には、#記号がついています(全部半角にしてくださいね)。これは、コメントという機能で、#より後に書かれた文字はPythonのスクリプトとして解釈されません。
そのため、プログラムの意味を説明したり、今回のように修正やデバッグなどで処理をスキップしたいときなどに使われます。後者は特に、コメントアウトと呼んだりします。
続く、pyperclip の copy関数は、その名の通り、クリップボードにコピーする関数です。ここでは、「こんにちは、パイソン(改行)」をコピーしています。
最後のPyAutoGUI の hotkey関数も、その名の通りですね。Ctrl + v というホットキー(ショートカットキー)を押す動作を実行しています。pyperclip の関数やPyAutoGUIの関数が交互に現れて混乱しがちですが、きちんと.の前もみて、今どのモジュール・パッケージを利用しているか、ということも意識してみてくださいね。
メモ帳の日本語入力をオフにして、紛らわしければ今までメモ帳に入力された文字も削除してから、chapter-4.pyを実行してください。
今度は期待通りに入力されましたね。
PyAutoGUIのwriteの動作は、ものすごく高速でキーボードを打鍵しているのと同じです。
日本語の環境では日本語入力のオン・オフで結果が変わってしまうなど、不安定な面を持ち合わせています。
そのため、ただ文字列を入力したい場合などは、クリップボード経由の方が、半角の文字を入力したい場合でも安定しやすいでしょう。
ホットキー・ショートカットキーの別の入力
純粋なショートカットキーの入力はhotkey関数が便利です。
ただ、事務系ソフト以外ではもう少し細かな制御をしたい場合があると思います。グラフィックツールなど、マウスとキーボード操作の組み合わせなどがその例ですね。
hotkeyで入力したCtrl + v というショートカットキーを別の方法で入力する方法を見てみます。
#ag.hotkey('ctrl', 'v)
ag.keyDown('ctrl') #Ctrlキーを押し下げる
time.sleep(0.1) #0.1秒待つ
ag.press('v') #vキーを押して離す
time.sleep(0.5) #0.5秒待つ
ag.keyUp('ctrl') #Ctrlキーを離す
より詳細にショートカットキー、特にCtrlのようなモディファイアキーの動作をコントロールできるのが分かると思います。
time.sleep() 関数により、Ctrlキーを押してから他のキーを押すまでの時間もコントロールできますので、処理の都合で少し待たなければいけない場合などはこちらの方法の方が便利でしょう。
- keyDown(key) #指定されたkeyを押した状態にする(キーが離されるまでそのまま)
- keyUp(key) #指定されたkeyを離す
- press(key) #指定されたkeyを押して離す(1文字入力。リスト形式角括弧[]でくくったコンマ区切りでは複数のキー入力)
- 例 ag.press([‘left’, ‘right’, ‘up’])
time.sleep(duration) #指定秒数だけ、プログラムの実行を停止します。
keyDownやpressなどで使える特殊キーの名前
出所(https://pyautogui.readthedocs.io/en/latest/keyboard.html#keyboard-keys)
['\t', '\n', '\r', ' ', '!', '"', '#', '$', '%', '&', "'", '(',
')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`',
'a', 'b', 'c', 'd', 'e','f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
'accept', 'add', 'alt', 'altleft', 'altright', 'apps', 'backspace',
'browserback', 'browserfavorites', 'browserforward', 'browserhome',
'browserrefresh', 'browsersearch', 'browserstop', 'capslock', 'clear',
'convert', 'ctrl', 'ctrlleft', 'ctrlright', 'decimal', 'del', 'delete',
'divide', 'down', 'end', 'enter', 'esc', 'escape', 'execute', 'f1', 'f10',
'f11', 'f12', 'f13', 'f14', 'f15', 'f16', 'f17', 'f18', 'f19', 'f2', 'f20',
'f21', 'f22', 'f23', 'f24', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9',
'final', 'fn', 'hanguel', 'hangul', 'hanja', 'help', 'home', 'insert', 'junja',
'kana', 'kanji', 'launchapp1', 'launchapp2', 'launchmail',
'launchmediaselect', 'left', 'modechange', 'multiply', 'nexttrack',
'nonconvert', 'num0', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6',
'num7', 'num8', 'num9', 'numlock', 'pagedown', 'pageup', 'pause', 'pgdn',
'pgup', 'playpause', 'prevtrack', 'print', 'printscreen', 'prntscrn',
'prtsc', 'prtscr', 'return', 'right', 'scrolllock', 'select', 'separator',
'shift', 'shiftleft', 'shiftright', 'sleep', 'space', 'stop', 'subtract', 'tab',
'up', 'volumedown', 'volumemute', 'volumeup', 'win', 'winleft', 'winright', 'yen',
'command', 'option', 'optionleft', 'optionright']
メモ帳を使ってマウスをちゃんと扱う
では、マウスを使ってメモ帳のウィンドウを操作してみます。
PyAutoGUI ではウィンドウを特定する手段がありませんので、座標も当然、スクリーン座標系となります。座標系について、詳しくは以下の過去記事を参照してください。
ざっとでいいよ、という場合には、「画面全体」か、「ウィンドウの中だけ」か、というような違いだと思ってください。PyAutoGUIの場合は、常に画面全体の座標を扱います。そして、PCでは左上が原点の(0, 0)、右下が一番大きい数値、フルHDなら(1920, 1080)となります。学校で習ったグラフの座標とは少し違うので気をつけてくださいね。
では、このスクリーン座標でメモ帳の指定の場所をクリックするにはどうすればいいか? それはメモ帳の原点(左上の点)を取得することです。
メモ帳の中の指定座標(ウィンドウ座標)をクリックしたいときはどうするか?
答えは、(希望する)ウィンドウ座標に対して、メモ帳の原点のスクリーン座標を足せば希望するウィンドウ座標の、スクリーン座標が分かります。
たとえば、メモ帳のウィンドウの左上ががスクリーン座標で(150,50)の位置にあったとします。そして、メモ帳のウィンドウの中で(250,100)の座標をクリックしたい、とします。
そうすると、ウィンドウ座標系のスタート(原点0, 0)は、スクリーン座標での(150,50)ですから、希望するウィンドウ座標(250, 100)は、スクリーン座標でいうと
(250, 100) + (150, 50) = (400, 150)
となります。イメージは、上の図で分かっていただけると思います。ピンと来ない方は、とりあえず、x, y の数値を足し合わせればいいんだな……くらいに思っておいてください。実際、こういう処理は慣れてくると直感で、「こうだな」と思いついてから、証明するような形で検算することが(筆者は)よくあります。分からないな、と思っても場合によっては手を止めずに進めることも大事です。学校の理科の実験のようなもので、動かすことで理解が深まります。
ウィンドウの座標を取得する(PyGetWindow)
マウスを扱うためにウィンドウ座標を取得する必要があることが分かりました。
ウィンドウの座標を知るには、ウィンドウを調べる・またはウィンドウに尋ねれば分かりそうです(オブジェクト指向の考え方)。
#wx = memo.left #左端のx座標のみ
#wy = memo.top #上端のy座標のみ
wx, wy = memo.topleft #左端と上端
#はコメントでしたから、上2行は実行されません。ただ、上2行をまとめたものが一番下の1行になっています。topleftという名前なので、Y座標、X座標という順番で来そうですが、これはおそらく英語での呼び方の慣習でtopleftなのだと思います(日本語で左上といっても上左とは言わないようなもの)。
そして、他のプログラミング言語の経験がある方は、
wx, wy =
という表現に違和感があるかもしれません。これは、アンパック(unpack) というPython の記法で、リスト(配列)やタプル(編集できないリストのようなもの)、果ては、’hello, python’のような文字列も、複数のものがひとまとまりになっているものを分解して代入してくれる方法となっています。
この場合、単に、
wcoord = memo.topleft
とした場合、X座標にはwcoord[0], Y座標にはwcoord[1]としてアクセス(使用)しなければならないところ、wx, wyと直感的に分かりやすい表記でアクセスできるのが利点です。
もし、これらの結果が知りたい場合は、
print(wx, wy)
という1行を加えてみましょう。実行時に、メモ帳のX座標、Y座標が
2343 266
のような形で表示されます。または、Python の対話モードを使ってもいいですね。わざわざprintしなくても値が確認できるので便利ですよ。
タイトルバーを標準的な方法でドラッグしてみる
ウィンドウの左上のスクリーン座標が分かれば、タイトルバーの位置はそれほど難しくないので、まずはタイトルバーのドラッグから試して見ましょう。
ag.moveTo(wx + 60, wy + 10) #メモ帳のアイコンの右くらいにカーソルを移動
ag.dragRel(50, 100, 0.5) #そこから右(X方向)に50, 下(Y方向)に0.5秒かけて移動
time.sleep(1) #次の動作まで1秒待つ
print('wx, wy', wx, wy)
print(memo.topleft) #wx, wyと今の位置を比較
ag.dragTo(wx, wy, 0.5) #マウスカーソルを、元々メモ帳の左上があった位置に移動(元の位置から-60, -10の位置)
time.sleep(1) #次の動作まで1秒待つ
ag.dragRel(60, 10, 0.5) #そこから60, 10の位置に移動(大体ウィンドウの位置が戻る)
各行の動作についてはコメントに記載した通りですが、まずは各関数の説明です。
- moveTo(x, y, duration) #x, yの座標に向けて、duration秒かけてマウスカーソルを動かす。durationは省略すると0秒(瞬時)。
- dragTo(x, y, duration) #現在の位置からx, yの座標に向けて、duration秒かけてマウスをドラッグする。durationは省略すると0秒(瞬時)。
- moveRel(moveX, moveY, duration) #現在の位置から+moveX, +moveYした座標に向けて、duration秒かけてマウスカーソルを動かす。マイナスの値も可能。durationは省略すると0秒(瞬時)。
- dragRel(moveX, moveY, duration) #現在の位置から+moveX, +moveYした座標に向けて、duration秒かけてマウスをドラッグする。マイナスの値も可能。durationは省略すると0秒(瞬時)。
まとめると、動かすだけでボタン操作はしないmove系と、マウスボタンを押してから動かし始め、動かし終わったらマウスボタンを放す(ドラッグする)drag系があります。そしてそれぞれ、スクリーン座標の絶対値を指定するTo系と、現在地からどれくらい離れているか(相対座標)を指定するRel系に分かれています。
print(‘文字列や変数’) #内容をコマンドプロンプトなどに出力します。文字列は””や”でくくり、変数はそのまま入力します。また、,で区切って複数まとめて表示できます。
time.sleep(duration) #指定秒数だけ、プログラムの実行を停止します。
実際に RPA を動作させて、コメントと上記の説明を見ればどういう動き・意味の関数なのかは掴めると思います。
ただ、プログラミング・RPA初心者の皆さんに注目して欲しい点が2点あります。
注目・気をつけたい点1. printで出力される数値
wx, wyには、メモ帳のウィンドウの左上の座標が保存されています。
しかし、print 1回目と2回目で出力される数値は異なっていますね? これは、ウィンドウが移動したため、直接メモ帳のウィンドウの座標を取得する、.topleft は常に最新の左上の座標をさしますが、wx, wyは、=で代入(コピー)されたタイミングの数値を、もう一度代入されるまで保存し続けるということです。
ag.moveTo(wx + 60, wy + 10) というプログラムで、wx, wyの数値が変化しているように感じるかもしれません。しかし、ここではmoveToに、wxに60を足した数値、wyに10を足した数値を、新しく作って渡しているだけで、wx, wyには= を使って代入していないので、変化していません。
そのため、マウスカーソルをwx, wyの位置までドラッグして、そこから更に60, 10 ずつ動かすことでウィンドウの位置を元に戻すことができています。
よく分からなかったら、数字を実際に自分の手で計算してみてくださいね。
注目・気をつけたい点2. マウスを手動で動かす
このプログラムでは、マウスの動作の間にsleep(1)を挟んで、敢えて1秒動作を待たせています。
この1秒の間に、マウスカーソルを動かしてみましょう。
動かし方にもよりますが、 RPA が全く意図したように動かなくなると思います。これは、move, drag系の関数が終点しか指定しないためです(実際に動かしてどうなるかを確認しましょう)。
これを避けるには、moveTo(x, y, 0)で動作する前に、始点を指示すること。
そして、そもそも RPA の作動中にはユーザーができるだけ操作しないようにすることが重要です。
タイトルバーを少し面倒くさい方法でドラッグしてみる
では続いて、もう1つの方法でウィンドウをドラッグしてみましょう。1つ前の節と大体同じような動きをさせてみます。
何度も同じ動作を見るのが面倒な場合は、import文以外を削除しておいても大丈夫ですよ。
memo = gw.getWindowsWithTitle('メモ帳')[0]
memo.activate()
wx, wy = memo.topleft #左端と上端
ag.moveTo(wx + 60, wy + 10) #メモ帳のアイコンの右くらいにカーソルを移動
ag.mouseDown() #マウスカーソルは動かさずにマウスボタンを押す。
ag.moveRel(50, 100, 0.5) #そこから右(X方向)に50, 下(Y方向)に0.5秒かけて移動
time.sleep(1) #次の動作まで1秒待つ
ag.moveTo(wx, wy, 0.5) #マウスカーソルを、元々メモ帳の左上があった位置に移動(元の位置から-60, -10の位置)
time.sleep(1) #次の動作まで1秒待つ
ag.moveRel(60, 10, 0.5) #そこから60, 10の位置に移動(大体ウィンドウの位置が戻る)
ag.mouseUp() #マウスカーソルは動かさずにマウスボタンを離す。
ぱっと見で余り変わっていないと思いますが、大事な点が2点あります。
drag系の関数がなくなり、全てmove系の関数になっています。それと、mouseDown, mouseUp関数が新たに追加されていますね(print関数は省略しました)。
- mouseDown() マウスの左ボタンを押して、そのままにする。
- mouseUp() マウスの左ボタンを離す。
- mouseDown(button=’right’) マウスの右ボタンを押して、そのままにする。button=’middle’で中央ボタン(ホイールボタン)。
- mouseUp(button=’right’) マウスの右ボタンを離す。button=’middle’で中央ボタン(ホイールボタン)。
mouseDown, MouseUpは、drag系や、この後紹介するclick関数と異なり、マウスボタンの押し込みと解放を個別にコントロールできる関数となっています。
ただし、プログラムは通常、マウスが押しっぱなしであるかを常に監視しておらず、マウスボタンが押されたら離されたという行動が通知されるまで、マウスボタンは押されたままだと判断します。
これがどういうことかというと、今回の RPA を実行している最中に、一度マウスを手動でクリックしてみてください。そうすると、マウスボタンが離された後、 RPA では mouseUp()関数を実行していないのに、ドラッグが中断されてマウスカーソルだけが動く様になります(実際にやってみてください!)。
さらに、sleepの時間待機中に、マウスカーソルを手で動かしたときの動作も異なります。マウスボタンを触っていない限り、メモ帳(Windows)はマウスボタンが押されっぱなしだと判断していますので、sleep中にも関わらず、前の節と異なりメモ帳のウィンドウがドラッグされます。
通常の RPA ではあまり使わないかもしれませんが、ドラッグしたまま(ボタンを離さずに)色々なところをなぞりたいときなどに使用しますので、この方法も覚えておきましょう。
メモ帳を標準的な方法でクリックしてみる
続いてクリックを実現してみましょう。chapter-4.pyの続きから、またはimport部分以外全て削除してもいいです(#でコメントアウトでもいいですね)。
memo = gw.getWindowsWithTitle('メモ帳')[0]
memo.activate()
wx, wy = memo.topleft #左端と上端
ag.click(wx + 20, wy + 10) #メモ帳のアイコンをクリック
time.sleep(1)
ag.click(wx + 20, wy + 10) #もう1度クリックしてメニューを消す
time.sleep(0.1)
ag.rightClick(wx + 20, wy + 10) #メモ帳のアイコンを右クリック
time.sleep(1)
ag.press('esc') #escキーを押してコンテキストメニューを消す
time.sleep(0.1)
ag.click(wx + 20, wy + 10, button='right') #右クリックの別のバージョン, button='middle'でホイールクリック
time.sleep(1)
ag.press('esc') #escキーを押してコンテキストメニューを消す
他のmouse系関数と同じで、座標を引数として受け取ります。
いろいろなクリックの色々な書き方
ag.click() # カーソルを動かさないでクリック
ag.rightClick(x, y) # ag.click(x, y, button='right')
ag.middleClick(x, y) #ag.click(x, y, button='middle')
ag.doubleClick(x, y) #ag.click(x, y, clicks = 2)
ag.tripleClick(x, y) #ag.click(x, y, clicks = 3)
ag.click(clicks = 2, interval = 0.25) #カーソルを動かさないで、クリック感覚を0.25秒間をあけたダブルクリック
このように PyAutoGUIでは同じクリックにしてもいろいろな書き方が用意されています。
動作に違いは発生しないので、書きやすい方法(覚えるのが面倒ならclick関数で全て実現できます)で書くのがいいでしょう。実際のところは、button=’right’とか書くのは少し面倒なので、通常の RPA を書くときは rightClick() のような書き方が便利だと思います。
では、click(button=”)はどういうときに使うでしょうか? かなり高度な(複雑な) RPA となってしまいますが、状況に応じて右クリックしたり左クリックしたりが切り替わるような場合に、変数にどのボタンをクリックするか記載する場合などがあります。
特に、レアな環境ですがマウスの設定で左右クリックの入れ替えを行っている場合(マウスとトラックパッドを併用するノートPCなどであります)、RPA の 左右クリックが狂ってしまう場合があります。そういうときに、
ButtonLeft = 'right'
ButtonRight = 'left'
ag.click(x, y, button = ButtonLeft)
のように変数を指定しておくと、変数に代入する文字列を変えるだけで RPA がきちんと動作するようになります(とはいえ、相当なレアケースなので「そうなんだ~」くらいで大丈夫です)。
また、ドラッグのときも利用したmouseDown, mouseUp関数でも当然、クリックを再現することができます。
ag.mouseDown(x, y)
time.sleep(0.15)
ag.mouseUp(x, y)
このように記載することで、マウスボタンを押してから、離すまでの時間を細かく調整した処理にすることもできます。
おわりに
今回は PyAutoGUI, PyGetWindow を使って RPA を作成する関数を一通りご紹介しました。このふたつのパッケージのいいところは、Windows 以外でも動作することです。
あまり多くないですが、筆者も Macを使うデザイナーの業務の効率化を相談されることがあります。Windows と色々違いますが、 どちらでも使える RPA があるのは非常に便利ですね。
- PyAutoGUIとPyGetWindowだけで基本的な RPA が作成可能
- 日本語入力はクリップボードを経由するのが安全で確実
- Windows, Mac 両方で RPA が作れる
- マウス、キーボード両方とも、複数の書き方があるので楽な方法が選択できる。
Python で RPA シリーズ
https://ict-worker.com/skill/python-rpa/
1件のフィードバック