自動 GUI 操作ライブラリ PyAutoGUI を用いた業務自動化 (RPA)

ec_pyautogui python
ec_pyautogui



RPA(Robotic Process Automation)とは,ボット,または,仮想知的労働者(Digital Labor)による,業務を自動化する技術の一種と言われています.
働き方が見直されている昨今において,工場などの現場作業はもちろんのこと,デスクワークにおいても省人化や自動化の需要があるため注目されており,各社からいろんな RPA ソフトやツールが開発/販売されています.
「労働賃金は,時間でなく成果により決定される」という成果主義の社会においては,日々同じ繰り返しのルーチンワークをいつまでも続けていても全く賃金が上がらないため,知恵を絞って,ルーチンワークを削減もしくは自動化して,より付加価値の高いクリエイティブな仕事に時間をかけたくなります.

例えば,帳簿を自動作成するなど従来からあるような自動化は,エクセルやその他計算ソフトを専用に作ったり,または,API(Apprication Programing Interface)を用いることにより達成できていましたが,これらによる自動化は,専用のAPIやシステムについての学習が必要ですし,さらには個々のシステムに対しての対応がバラバラであることが多く,汎用性に欠けるというのがデメリットと言えます.
一方,世に言う RPA では GUI(Graphical User Interface)を用いた画面操作自体を自動操作させることが多く,上記のような API やシステムがソフト側に備わっていなくても自動化を可能としています.

RPA ツールは,他にも色んなことができますが Microsoft の Power Automate をはじめ,有料/無料のものがいくつもありますが,この記事では,Python のサードパーティライブラリの一つの PyAutoGUI を用いて RPA ツールを自作することを目標に,当該ライブラリと実装例について紹介します.
もちろん,完成された RPA ツールを用いたほうが簡単に操作できたり,動作の安定性が保証されたり,製造元によるサポートがあったりといった利点が挙げられますが,Python で RPA ツールを自作することの意味としては,一般にあるソフトでは不可能な細かな自分好みの最適設計ができたり,重量級な数値計算結果を RPA に反映できたり,構築した AI モデルによる推論を使うことができたりと,無数にある Python のリソースを扱えることです.
(筆者における PyAutoGUI を用いた一例として,リモートワークにおけるPC起動時のルーチンである, VPN接続 → 出退勤 web アプリによる出勤打刻 → ウィルス対策ソフト更新とスキャン → Visual Studio Installer 起動と更新 → VSCode 起動と更新 → メールソフト起動 → チャットソフト起動 → 最新の社内情報閲覧のための掲示板表示 といった一連の流れをワンクリックして放置するだけで良いように自動化させ,日々用いています)

当該記事のコードについては,以下の github に掲載しています.

https://github.com/KazutoMakino/PythonCourse/tree/main/009_pyautogui

また,PyAutoGUI の公式ドキュメントはこちらです.

Welcome to PyAutoGUI’s documentation! — PyAutoGUI documentation

PyAutoGUI のインストール

PyPI (The Python Package Index) に公開されていますので,

  • windows: pip install pyautogui (または,py -m pip install pyautogui)
  • Mac, Linux (WSL 含む): pip3 install pyautogui

にてインストール可能です(conda 環境であれば,pip を conda に変更してください).
もし,オフライン環境,あるいは,会社プロキシ環境下にてサードパーティライブラリを導入したい場合は,以下の記事を参考ください.

PyAutoGUI でできること

名前の通り GUI 操作を自動化できるのですが,具体的に列挙すると,以下が実行可能です.

  • 画面情報の取得・・・・画面サイズなどの取得
  • マウス操作・・・・マウス位置取得,移動,クリック,クリックしたまま,ダブルクリック,右クリックなど
  • キーボード操作・・・・単一や組み合わせのキー入力(例えば ctrl + v など)
  • メッセージボックスの表示・・・・通知,ボタン選択(例えば 「はい」と「キャンセル」など),任意の入力を受け取るウィンドウ,パスワード入力ウィンドウ
  • スクリーンショット関連・・・・スクリーンショットを撮る,画像ファイルと画面上にある画像領域をパターンマッチングさせて位置を取得するなど(この機能を使うには opencv-python が必要ですので,pip install してください)

マウス操作について

新規の .py ファイルを作成し,以下のコードをコピペして,実行してみてください.

import pyautogui as pg

print("pg.size")
width, height = pg.size()
print(f"(width, height) = ({width}, {height})")
print()

print("pg.position")
mouse_x, mouse_y = pg.position()
print(f"(mouse_x, mouse_y) = {mouse_x}, {mouse_y}")
print()

pg.moveTo(x=500, y=300, duration=1.2, logScreenshot=False)
mouse_x, mouse_y = pg.position()
print(f"pg.moveTo(x=500, y=300) -> (mouse_x, mouse_y) = {mouse_x}, {mouse_y}")

print("pg.onScreen(x=0, y=0):", pg.onScreen(x=0, y=0))
print("pg.onScreen(x=0, y=-1):", pg.onScreen(x=0, y=-1))
print("pg.onScreen(x=width, y=height):", pg.onScreen(x=width, y=height))
print("pg.onScreen(x=width - 1, y=height - 1):", pg.onScreen(x=width - 1, y=height - 1))
print()

print("pg.vscroll")
pg.vscroll(clicks=100)
print()

print("pg.dragTo")
pg.dragTo(
    x=100, y=500, duration=1.1, button="right", logScreenshot=False, mouseDownUp=True
)
print()

print("pg.rightClick")
pg.rightClick(x=500, y=200, interval=1.3, duration=1.8, logScreenshot=False)

最初の行にて,pyautogui を pg として import しています.
以降のブロックごとにピックアップして解説します.

pg.size()

・・・・メインモニタの縦と横のピクセルサイズが返されます.

pg.position()

・・・・現在のマウスの位置(x, y 座標)を取得します.

pg.moveTo(x, y, duration=0, logScreenshot=False)

・・・・マウスを x, y に duration [s] かけて動かします.
moveTo は絶対座標を指定しますが,相対座標を指定したい場合は move を用います.
それぞれの引数の説明は以下です.

x, y (int, int):
    マウスを動した先の座標(原点はモニタの左上の (0, 0))
duration (float):
    何秒かけて x, y に移動させるか(初期値:0)
logScreenshot (bool)
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)
pg.onScreen(x, y)

・・・・指定する x, y 座標がスクリーン上にあるかどうかの判定を返します.

pg.vscroll(clicks, x=None, y=None, logScreenshot=False)

・・・・x, y 座標で clicks の分だけ縦スクロールします.
横スクロールしたい場合は hscroll を用います.
それぞれの引数の説明は以下です.

clicks (int, float):
    スクロールする量
x, y (int):
    スクロールさせるときのマウス位置(初期値:None)
logScreenshot (bool):
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)
pg.dragTo(x, y, duration=0, button="right", logScreenshot=False, mouseDownUp=True)

・・・・x, y に duration [s] かけてマウスに割り当てられた button を押しながらドラッグします.
dragTo は絶対座標指定ですが,相対座標を指定したい場合は drag を用います.
それぞれの引数の説明は以下です.

x, y (int, int):
    マウスを動した先の座標(原点はモニタの左上の (0, 0))
duration (float):
    何秒かけて x, y に移動させるか(初期値:0)
button (str):
    ドラッグ操作に用いるマウスボタン(初期値:"PRIMARY" ("left" と同じ)).
    "left", "right", "middle" が有効.
logScreenshot (bool):
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)
mouseDownUp (bool):
    True の場合ドラッグ終了後ボタンを離し,False はボタンを押下したままにする
pg.rightClick(x, y, interval=0, duration=0, logScreenshot=False)

・・・・x, y に duration [s] かけて右クリックします.
leftClick, middleClick, doubleClick などももちろんありますが,誤操作防止の為,今回の例では用いていません.
それぞれの引数の説明は以下です.

x, y (int, int):
    マウスを動した先の座標(原点はモニタの左上の (0, 0))
interval (float):
    x, y 移動後に何秒待つか(初期値:0)
duration (float):
    何秒かけて x, y に移動させるか(初期値:0)
logScreenshot (bool):
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)

キーボード操作

新規の .py ファイルを作成し,以下のコードをコピペして,実行してみてください.

import time

import pyautogui as pg

print("pg.press")
pg.press(keys="win", presses=1, interval=1, logScreenshot=False)
time.sleep(1)
print()

print("pg.write")
pg.write(message="calc", interval=0, logScreenshot=False)
time.sleep(1)
print()

print("pg.keyDown / pg.press")
pg.keyDown(key="win", logScreenshot=False)

pg.press(keys="e", interval=1)
pg.press(keys="down", presses=2, interval=1)
print()

print("pg.keyUp")
pg.keyUp(key="win", logScreenshot=False)
print()

print("pg.hold / pg.press")
with pg.hold(keys="win", logScreenshot=False):
    for _ in range(3):
        pg.press(keys="e")
        time.sleep(1)
print()

print("pg.hotkey")
pg.hotkey("ctrl", "w")

ブロックごとにコードの説明をしていきます.

pg.press(keys, presses=1, interval=0, logScreenshot=False)

・・・・keys で指定したキーを presses 回押します.
それぞれの引数の説明は以下です.

keys (str, list):
    pg.KEYBOARD_KEYS で確認できるキーボード上のキー
presses (int):
    keys を押す回数(初期値:1)
interval (float):
    presses 回繰り返し押すときの待ち時間(初期値:0)
logScreenshot (bool):
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)
pg.write(message, interval, logScreenshot)

・・・・message の文字列一文字ずつを interval 秒ずつ空けて入力します.
それぞれの引数の説明は以下です.

message (str, list):
    入力する文字列(日本語入力したい場合は,pyperclip.copy(文字列) → pg.hotkey("ctrl", "v"))
interval (float):
    message 一文字ずつ(list の場合は一つずつ)入力するときの待ち時間
    (初期値:0)
logScreenshot (bool):
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)
pg.keyDown(key, logScreenshot)

・・・・ key を押した状態にします.
それぞれの引数の説明は以下です.

key (str)
    pg.KEYBOARD_KEYS で確認できるキーボード上のキー
logScreenshot (bool):
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)
pg.keyUp(key, logScreenshot)

・・・・ key を離した状態にします.
それぞれの引数の説明は以下です.

key (str)
    pg.KEYBOARD_KEYS で確認できるキーボード上のキー
logScreenshot (bool):
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)
pg.hold(keys, logScreenshot)

・・・・keys を押したままにします.
with ブロックを使うと,ブロックを抜けたときに解除できます.
それぞれの引数の説明は以下です.

keys (str, list):
    pg.KEYBOARD_KEYS で確認できるキーボード上のキー
logScreenshot (bool):
    動作終了時にタイムスタンプ名称でスクリーンショットを保存するかどうか
    (初期値:False)
pg.hotkey(*args, *keywords)

・・・・複数のキーの同時押しができます.

メッセージボックスの表示

新規の .py ファイルを作成し,以下のコードをコピペして,実行してみてください.

import pyautogui as pg

print("pg.alert")
print(pg.alert(text="テキスト", title="pg.alert", button="閉じる", timeout=5000))
print()

print("pg.confirm")
print(
    pg.confirm(
        text="テキスト", title="pg.confirm", buttons=["はい", "いいえ", "キャンセル"], timeout=5000
    )
)
print()

print("pg.prompt")
print(pg.prompt(text="テキスト", title="pg.prompt", default="デフォルト", timeout=5000))
print()

print("pg.password")
print(
    pg.password(
        text="テキスト", title="pg.password", default="デフォルト", mask="x", timeout=5000
    )
)

ブロックごとにコードの説明をしていきます.

pg.alert(text="", title="", button="", timeout=None)

・・・・警告のメッセージボックスを出します.
戻り値は button の値です.
それぞれの引数の説明は以下です.

text (str): メッセージボックスで表示するテキスト (初期値:"")
title (str): メッセージボックスウィンドウのタイトル (初期値:"")
button (str): メッセージボックスのボタンのテキスト (初期値:"")
timeout (int):
    メッセージボックスを表示する時間制限 [ms].
    指定時間をすぎるとメッセージボックスが閉じ,"Timeout" が返される.
    (初期値:None)
pg.confirm(text="", title="", buttons=["OK", "Cancel"], timeout=None)

・・・・警告のメッセージボックスを出します.
戻り値は buttons の内押されたボタンの値です.
それぞれの引数の説明は以下です.

text (str): メッセージボックスで表示するテキスト (初期値:"")
title (str): メッセージボックスウィンドウのタイトル (初期値:"")
buttons (list): メッセージボックス内の複数のボタンのテキスト (初期値:"")
timeout (int):
    メッセージボックスを表示する時間制限 [ms].
    指定時間をすぎるとメッセージボックスが閉じ,"Timeout" が返される.
    (初期値:None)
pg.prompt(text="", title="", default="", timeout=None)

・・・・テキスト入力部のあるメッセージボックスを出します.
戻り値は入力したテキストです.
それぞれの引数の説明は以下です.

text (str): メッセージボックスで表示するテキスト (初期値:"")
title (str): メッセージボックスウィンドウのタイトル (初期値:"")
default (str): テキスト入力部の初期値 (初期値:"")
timeout (int):
    メッセージボックスを表示する時間制限 [ms].
    指定時間をすぎるとメッセージボックスが閉じ,"Timeout" が返される.
    (初期値:None)
pg.password(text="", title="", default="", mask="*", timeout=None)

・・・・テキスト入力部のあるメッセージボックスを出します.
戻り値は入力したテキストです.
それぞれの引数の説明は以下です.

text (str): メッセージボックスで表示するテキスト (初期値:"")
title (str): メッセージボックスウィンドウのタイトル (初期値:"")
default (str): テキスト入力部の初期値 (初期値:"")
mask (str): パスワード入力を隠す文字列 (初期値:"*")
timeout (int):
    メッセージボックスを表示する時間制限 [ms].
    指定時間をすぎるとメッセージボックスが閉じ,"Timeout" が返される.
    (初期値:None)

スクリーンショット関連について

新規の .py ファイルを作成し,以下のコードをコピペして,実行してみてください.

from pathlib import Path
from pprint import pprint

import pyautogui as pg

img_path = "./screenshot.jpg"

img = pg.screenshot(imageFilename=img_path, region=(100, 100, 800, 600))
print("--- pg.screenshot")
print(f"img.size = {img.size}")
print(f"file exists: {Path(img_path).exists()}")
print()

location = pg.locateOnScreen(img_path, confidence=0.9, grayscale=False)
print("--- pg.locateOnScreen")
print(f"location = {location}")
print(f"type(location) = {type(location)}")
print(f"location.left = {location.left}")
print()

location = pg.locateCenterOnScreen(img_path, confidence=0.9, grayscale=False)
print("--- pg.locateCenterOnScreen")
print(f"location = {location}")
print()

locations = pg.locateAllOnScreen(img_path, confidence=0.9, grayscale=False)
print("--- pg.locateAllOnScreen")
print(f"locations = {locations}")
print(f"type(locations) = {type(locations)}")
print("list(locations) =")
pprint(list(locations))

ブロックごとにコードの説明をしていきます.

pg.screenshot(imageFilename=None, region=None)

・・・・スクリーンショットを取得します.
戻り値はスクリーンショットの画像の PIL.Image.Image です.
それぞれの引数の説明は以下です.

imageFilename (str): パス文字列を指定した場合は,スクリーンショットが保存される
    (初期値: None・・・・スクリーンショットは保存されない)
region (tuple): 左上の x, y 座標と幅と高さ (x, y, w, h) を指定することで
    スクリーンショットの領域を指定することができる
    (初期値: None ・・・・全体の領域)
pg.locateOnScreen(filepath, confidence=1.0, grayscale=False)

・・・・filepath で指定した画像と画面をパターンマッチングし,適合した画像の左上の x, y 座標と幅と高さを取得する.候補が複数ある場合は,一番左上の値を取得します.
cv2.matchTemplate を用いるため opencv-python が必要です.
また,opencv は日本語を受け付けないため,パス文字列には日本語が入らないようにする必要があります.
それぞれの引数の説明は以下です.

filepath (str): パターンマッチングする画像ファイルのパス文字列.
confidence (float): パターンマッチングする精度で,1.0 未満を指定することで,
    少しのピクセル誤差であれば許容することができ,パターンマッチングが成功しやすくなる (初期値: 1.0)
grayscale (bool): パターンマッチングするときに互いをグレイスケールで比較することで,
    高速化させることができるが精度は低くなる (初期値: False)
pg.locateCenterOnScreen(filepath, confidence=1.0, grayscale=False)

・・・・filepath で指定した画像と画面をパターンマッチングし,適合した画像の中心の x, y 座標を取得します.
候補が複数ある場合は,一番左上の値を取得します.
cv2.matchTemplate を用いるため opencv-python が必要です.
また,opencv は日本語を受け付けないため,パス文字列には日本語が入らないようにする必要があります.
それぞれの引数の説明は以下です.

filepath (str): パターンマッチングする画像ファイルのパス文字列.
confidence (float): パターンマッチングする精度で,1.0 未満を指定することで,
    少しのピクセル誤差であれば許容することができ,パターンマッチングが成功しやすくなる (初期値: 1.0)
grayscale (bool): パターンマッチングするときに互いをグレイスケールで比較することで,
    高速化させることができるが精度は低くなる (初期値: False)
pg.locateAllOnScreen(filepath, confidence=1.0, grayscale=False)

・・・・filepath で指定した画像と画面をパターンマッチングし,適合した全ての画像の左上の x, y 座標と幅と高さをジェネレータとして返します.
cv2.matchTemplate を用いるため opencv-python が必要です.
また,opencv は日本語を受け付けないため,パス文字列には日本語が入らないようにする必要があります.
それぞれの引数の説明は以下です.

filepath (str): パターンマッチングする画像ファイルのパス文字列.
confidence (float): パターンマッチングする精度で,1.0 未満を指定することで,
    少しのピクセル誤差であれば許容することができ,パターンマッチングが成功しやすくなる (初期値: 1.0)
grayscale (bool): パターンマッチングするときに互いをグレイスケールで比較することで,
    高速化させることができるが精度は低くなる (初期値: False)

演習問題

何でも良いので RPA ツールを簡単に自作してみましょう.

もし,テーマに悩まれるようでしたら,windows 限定ですが 「ディスプレイの拡大率の変更の自動化」にチャレンジしてみてください.
以下,解答例においても,当該テーマについて取り組みました.

演習問題の解答例

例として,「windows 10 におけるディスプレイの拡大率の変更」を自動化させる RPA ツールを作成してみました.

"""自動化の例: windows10 においてディスプレイの拡大率の変更を自動化

References:
- https://pyautogui.readthedocs.io/en/latest/

Blog:
- https://slash-z.com/python-pyautogui/

---

KazutoMakino

"""

import time

import pyautogui as pg
import pyperclip

# 拡大させるのか縮小させるのかを取得する
ret = pg.confirm(
    text="テキスト、アプリ、その他の項目のサイズを拡大させますか?\nそれとも縮小させますか?",
    title="テキスト、アプリ、その他の項目のサイズの変更",
    buttons=["拡大", "縮小", "キャンセル"],
)
print(f"拡大? or 縮小? : {ret}")
if ret == "拡大":
    mode = "expansion"
elif ret == "縮小":
    mode = "shrink"
else:
    exit()

# このターミナルのウィンドウを右に寄せる
print("このターミナルのウィンドウを右に寄せる")
pg.hotkey("win", "right")
time.sleep(1)

# ディスプレイの設定を開く
print("ディスプレイの設定を開く")
pyperclip.copy(text="テキスト、アプリ、その他の項目のサイズを変更する")
pg.press(keys="win")
pg.hotkey("ctrl", "v")
time.sleep(1)
pg.press(keys="enter")
time.sleep(3)

# 設定のウィンドウを左に寄せる
print("設定のウィンドウを左に寄せる")
pg.hotkey("win", "left")
time.sleep(1)

if mode == "expansion":
    # 拡大率が 100 % のときの "テキスト、アプリ、その他の項目のサイズを変更する" の項目のドロップダウンの位置を取得する
    print('拡大率が 100 % のときの "テキスト、アプリ、その他の項目のサイズを変更する" の項目のドロップダウンの位置を取得する')
    locate = pg.locateOnScreen("./img/01.jpg", confidence=0.95, grayscale=True)
    x, y = locate.left + 141, locate.top + 91

    # ドロップダウンにマウスを移動し左クリックする
    print("ドロップダウンにマウスを移動し左クリックする")
    pg.moveTo(x=x, y=y, duration=1)
    pg.leftClick()
    time.sleep(1)

    # ドロップダウンから拡大率 150 % を選択してクリックする
    print("ドロップダウンから拡大率 150 % を選択してクリックする")
    locate = pg.locateOnScreen("./img/02.jpg", confidence=0.95, grayscale=True)
    x, y = locate.left + 60, locate.top + 45
    pg.moveTo(x=x, y=y, duration=1)
    pg.leftClick()

elif mode == "shrink":
    # 拡大率が 150 % のときの "テキスト、アプリ、その他の項目のサイズを変更する" の項目のドロップダウンの位置を取得する
    print('拡大率が 150 % のときの "テキスト、アプリ、その他の項目のサイズを変更する" の項目のドロップダウンの位置を取得する')
    locate = pg.locateOnScreen("./img/03.jpg", confidence=0.95, grayscale=True)
    x, y = locate.left + 215, locate.top + 70

    # ドロップダウンにマウスを移動し左クリックする
    print("ドロップダウンにマウスを移動し左クリックする")
    pg.moveTo(x=x, y=y, duration=1)
    pg.leftClick()
    time.sleep(1)

    # ドロップダウンから拡大率 100 % を選択してクリックする
    print("ドロップダウンから拡大率 100 % を選択してクリックする")
    locate = pg.locateOnScreen("./img/04.jpg", confidence=0.95, grayscale=True)
    x, y = locate.left + 70, locate.top + 25
    pg.moveTo(x=x, y=y, duration=1)
    pg.leftClick()

time.sleep(1)

# 完了ウィンドウを出す
print("完了ウィンドウを出して終了")
pg.alert(text="全ての動作が正常終了しました", title="完了")

Python を用いた自動化についてのオススメの書籍

以下書籍は,「退屈なことは Python にやらせよう」というタイトルの通り,日常の事務作業におけるデータシート作成,図形描画,メール操作,あるいはGUI操作など決まった処理をPythonを用いて自動化させようという書籍です.
自動化処理の実装だけでなくPythonの基礎や各ライブラリの説明など詳しく書かれているため,「Pythonで何かの処理を自動化させたい」という方に特にオススメです.
※ 「ノンプログラマー」というフレーズがありますが,もちろんプログラミングはします.

コメント

  1. 研究職 佐藤 より:

    Pyautoguiをオフライン環境でインストールしたいと思っています.
    そこで,質問なのですが,ネット上にwhlファイルがない場合どのようにインストールすればよいでしょうか?
    教えていただきたいです.

    • kazutomakino より:

      ご質問ありがとうございます.
      返信遅れまして恐縮です.
      pip 環境であれば PyPI の PyAutoGUI のページ (https://pypi.org/project/PyAutoGUI/#files) よりダウンロードした .tar.gz をローカル環境で pip install すれば良いです.
      .whl 以外のローカルファイルも大体は pip install できます.
      ぜひお試しください!
      (embeddable python 使うとか docker とか用いる方法でも良いと思います)

タイトルとURLをコピーしました