9
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Python RPA大全(pyautoguiと愉快な仲間たち)

Last updated at Posted at 2023-08-18

はじめに

Pyautoguiがあれば大概のRPAが実装できる。周辺モジュールも含めてRPAを実装するための大全をメモします。
この記事の情報でほとんどすべてのGUI操作が可能になるのではないでしょうか?

メインで使用するpyautoguipagとしてimportしておく。

import pyautogui as pag

キーボード操作

ユーザーIDやパスワードを有力する際に使用したり、ショートカットキーを送信するために使う。

キーを順番に押す

GUIのボタン操作などでtab連打からのenterを押したいときに便利。
pressesで複数回繰り返すことができる。
下の例ではTab→Enter→Tab→Enter→Tab→Enterと繰り返す。

pag.press(["tab", "enter"], presses=3)

キーを同時押しする

ショートカットキーを送信するときに便利。この例ではctrlを押したままaを押し、aを離したあとにctrlを離す。

pag.hotkey("ctrl", "a")

テキスト入力(英語)

str型のテキストを入力できる。

pag.write("text")

テキスト入力(日本語)

write()メソッドは日本度を入力できないため、一旦クリップボードを経由させる。
pyperclipモジュールを用いてクリップボードへアクセスする。

import pyperclip as clip
clip.copy("日本語の文章")
pag.hotkey("ctrl", "v")

キー名称の確認

入力できるキーの一覧は以下のように確認することができる。

print(pag.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']

マウス操作

基本的には相対/絶対座標を指定して移動、クリックを行う。
画面内の座標系は以下のようになっている。(公式引用)

0,0       X increases -->
+---------------------------+
|                           | Y increases
|                           |     |
|   1920 x 1080 screen      |     |
|                           |     V
|                           |
|                           |
+---------------------------+ 1919, 1079

マウスを動かす

move(x, y)で相対座標、moveTo(x, y)で絶対座標を指定してマウスを動かすことができる。
座標にNoneを指定するとその方向には移動しない。

pag.move(10, 10)
pag.moveTo(100, 100)

クリック

pag.click()で左クリックを行う。
x, yを指定すればmoveしてからクリックする。
clicksを指定すれば複数回の連続クリックを実行できる。
button"primary"を指定すればいわゆる左クリック、"secondary"を指定すればいわゆる右クリック、"middle"で中クリックを行える。

pag.click(x=100, y=300, clicks=2, button="primary")

ドラッグ

moveと同様に、drag(x, y)で相対座標、dragTo(x, y)で絶対座標を指定してマウスを動かすことができる。
座標にNoneを指定するとその方向には移動しない。

pag.drag(10, 10)
pag.dragTo(None, 10)

画面スクロール

scrollというメソッドが実装されているが、引数の定義が曖昧。
+で上方向、-で下方向に移動する。

pag.scroll(-3000)

テキストエディタなどでは使用しづらいが、場合によっては↓を複数回押したほうが良い場合がある。

pag.press("down", presses=3)

ソフトやアプリケーションの起動

起動系のコマンドは2つ、それぞれメリデメがある。

subprocess.Popen()

subprocess.Popenメソッドを使用すれば大概のプログラムを起動できる。
また、リスト形式で引数を渡して実行することもできる。

import subprocess
app = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
url = "https://www.google.com/"

subprocess.Popen([app, url])

os.startfile()

os.startfileメソッドを使用することでsubprocess.Popenでは開けないショートカットを開くことができる。

import os
app = r"C:\Users\Public\Desktop\shortcut.url"

os.startfile(app)

待機する

GUIに対してアクションをおこなった後、何かしら応答が返ってくるまで待ってから次の動作実行しないと予期せぬ暴走を引き起こしかねない。
ここではいくつか待機方法について紹介する。

シンプルに時間で待つ

予め決められた秒数だけ待ってから次の動作を実行する。time.sleepを使用するか、pyautoguisleepメソッドを使用する。

import time
time.sleep(30)
pag.sleep(30)

画像認識

画像を認識させ、予め準備しておいた画像と一致する部分があったら次の処理を行う。
picsに認識させたい画像のパスのリストを渡すと、見つかった位置とインデックスを返す。

画像の準備方法も下の方で紹介しています。

どの画像が見つかったかをインデックスで判断できるため、条件分岐の起点として使用できる。

import datetime
import time
def wait_pics(pics, dt=0.0, upto=600.0):
    abort_time = datetime.datetime.now() + datetime.timedelta(seconds=upto)
    while datetime.datetime.now() < abort_time:
        for index, pic in enumerate(pics):
            pag.failSafeCheck()
            loc = pag.locateCenterOnScreen(pic)
            if loc is not None:
                return loc, index
        time.sleep(dt)
    raise TimeoutError()

datetimeモジュールを使用して一定時間過ぎた場合はwhile文を抜けてTimeoutErrorを出力する。
picsを一つ一つlocateCenterOnScreenで探す。
見つからなかった場合はNoneが返ってくるため次の画像を探す。
見つかった場合はその座標と画像のインデックスを返す。

使用例は下記の通り。

# test1かtest2の画像がウィンドウに現れるまで待つ
loc, index = wait_pics(["test1.png","test2.png"])

# どちらのインデックスが見つかったか
print(index)
# 見つかった画像の位置へマウスを移動する
pag.moveTo(loc)

ウィンドウタイトルで判断

指定された文字列をタイトルに含むウィンドウが出現するまで待つ。基本的な思想は画像認識待ちと同様。
titlesに検索したいウィンドウ名の文字列リストを渡すと、見つかった位置とインデックスを返す。

import datetime
import time
import pygetwindow as gw
def wait_title(titles, dt=0.0, upto=600.0):
    abort_time = datetime.datetime.now() + datetime.timedelta(seconds=upto)
    while datetime.datetime.now() < abort_time:
        for index, title in enumerate(titles):
            pag.failSafeCheck()
            win = gw.getWindowsWithTitle(title)
            if win != []:
                return win[0], index
        time.sleep(dt)
    raise TimeoutError()

使用例は下記の通り。

# "Outlook"か"Chrome"を含むウィンドウが出現するまで待つ
win, index = wait_title(["Outlook", "Chrome"])

# どちらのインデックスが見つかったか
print(index)
# すべてのウィンドウを最小化
pag.hotkey("win", "d")
# 見つかったウィンドウをアクティベートして最大化
win.activate()
win.maximize()

マウスカーソルの状態で判断

マウスカーソルが砂時計状態ではなくなるまで待機し続ける。
適当な座標を入力するとカーソルを移動し、砂時計状態が終了したらカーソル状態を返す。

import datetime
import time
import win32gui
def wait_hourglass(x=None, y=None, dt=0.0, upto=600.0):
    abort_time = datetime.datetime.now() + datetime.timedelta(seconds=upto)
    while datetime.datetime.now() < abort_time:
        pag.failSafeCheck()
        pag.moveTo(x, y)
        cursor = win32gui.GetCursorInfo()[1]
        if cursor not in [65543, 65561]:
            return cursor
        time.sleep(dt)
    raise TimeoutError()

使用例は下記の通り。

wait_hourglass()

運用上は
画像を認識→マウス移動→ポインター待ち
が安定しそう。

ちなみに私調べでは

通常状態:65539
テキスト入力状態:65541
砂時計とポインター:65561
砂時計:65543

となっていました。このインデックスの調べ方に詳しい方ぜひ教えてください。

画像認識用の画像準備方法

  1. shift + win + sで認識したい画像をキャプチャする
  2. ペイントを起動する
    無題.png
  3. トリミングする
    なるべく小さく切り取ったほうが認識速度が上がるっぽい。
    本来認識に関係ない外側の部分に外乱が入って認識できないという残念なエラーを防ぐためにもきわきわで切り取っておく。
    image.png
    image.png
  4. *.png形式で保存する
    image.png
9
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?