はじめに
PythonでRPAを作成する場合、PyAutoGUI は非常に便利なライブラリです。マウス操作やキーボード操作をPythonから簡単に実行できます。
一方で、RPAを作成するときには、
pyautogui.click(500, 300)
pyautogui.write("Hello")
pyautogui.press("enter")
のようなコードを、操作のたびに手作業で記述する必要があります。
そこで今回は、人間の操作をJSON形式で記録し、そのJSONからPyAutoGUIのRPAコードを自動生成するという仕組みを作りました。この記事では、その中のコード生成部分について紹介します。
1. 作成したもの
今回作成したのは pyauto_generator.py です。全体の流れは次のようになっています。
actions.json
↓
pyauto_generator.py
↓
generated_rpa.py
例えば、入力となる actions.json に、
{
"actions": [
{
"type": "click",
"position": {
"x": 500,
"y": 300
},
"button": "left"
},
{
"type": "text",
"text": "Hello World"
},
{
"type": "key",
"key": "enter"
}
]
}
と記録されていた場合、
import pyautogui
import time
# =====================================
# RPA操作
# =====================================
pyautogui.click(500, 300, button='left')
pyautogui.write("Hello World")
pyautogui.press('enter')
というPythonコードを自動生成します。
2. なぜJSONを挟むのか
今回の仕組みでは、操作内容を直接PyAutoGUIのコードとして保存していません。
人間の操作
↓
操作イベント
↓
actions.json
↓
コード生成
↓
PyAutoGUI
という構成にしています。ここが今回の仕組みで重要な部分です。
例えば、操作を直接 pyautogui.click(500, 300) として記録してしまうと、PyAutoGUIに依存したデータになってしまいます。一方、
{
"type": "click",
"position": {
"x": 500,
"y": 300
},
"button": "left"
}
のようにしておけば、
actions.json
↓
┌─────┴─────┐
↓ ↓
PyAutoGUI 別のRPAライブラリ
というように、将来的に別のコード生成器を作ることもできます。つまり、操作データと実行ライブラリを分離することを狙っています。
3. pyauto_generator.py の全体像
現在のコードは以下のようになっています。
import json
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
INPUT_FILE = BASE_DIR / "actions.json"
OUTPUT_FILE = BASE_DIR / "generated_rpa.py"
def quote(value):
return repr(value)
key_map = {
"left windows": "winleft",
"right windows": "winright",
"windows": "win",
"win": "win",
}
def generate_action(action):
action_type = action["type"]
if action_type == "click":
x = action["position"]["x"]
y = action["position"]["y"]
button = action["button"]
return f"pyautogui.click({x}, {y}, button={quote(button)})"
elif action_type == "double_click":
x = action["position"]["x"]
y = action["position"]["y"]
button = action["button"]
return (
f"pyautogui.doubleClick("
f"{x}, {y}, button={quote(button)})"
)
elif action_type == "scroll":
x = action["position"]["x"]
y = action["position"]["y"]
delta = int(action["delta"])
return (
f"pyautogui.moveTo({x}, {y})\n"
f"pyautogui.scroll({delta})"
)
elif action_type == "drag":
button = action["button"]
start_x = action["start"]["x"]
start_y = action["start"]["y"]
end_x = action["end"]["x"]
end_y = action["end"]["y"]
duration = action["duration"]
return (
f"pyautogui.moveTo({start_x}, {start_y})\n"
f"pyautogui.mouseDown(button={quote(button)})\n"
f"pyautogui.moveTo({end_x}, {end_y}, duration={duration})\n"
f"pyautogui.mouseUp(button={quote(button)})"
)
elif action_type == "text":
text = action["text"]
return (
f'pyautogui.write("{text}")'
)
elif action_type == "key":
key = action["key"]
return(
f"pyautogui.press('{key}')"
)
elif action_type == "hotkey":
converted_keys = [
key_map.get(key.strip().lower(), key.strip().lower())
for key in action["keys"]
]
keys = ", ".join(repr(key) for key in converted_keys)
return (
f"pyautogui.hotkey({keys})"
)
elif action_type == "ime":
return(
"pyautogui.press('kanji')"
)
else:
raise ValueError(f"未対応のActionです: {action_type}")
def generate_rpa_code(data):
actions = data["actions"]
lines = [
"import pyautogui",
"import time",
"",
"",
"# =====================================",
"# RPA操作",
"# =====================================",
"",
]
for action in actions:
delay = action.get("delay", 0)
if delay > 0:
lines.append(f"time.sleep({delay})")
lines.append("")
lines.extend(generate_action(action).splitlines())
lines.append("")
return "\n".join(lines)
def main():
input_path = Path(INPUT_FILE)
output_path = Path(OUTPUT_FILE)
if not input_path.exists():
print(f"エラー: {INPUT_FILE} が見つかりません。")
return
try:
with input_path.open("r", encoding="utf-8") as f:
data = json.load(f)
except json.JSONDecodeError as e:
print("エラー: actions.jsonのJSON形式が正しくありません。")
print(e)
return
if "actions" not in data:
print('エラー: actions.jsonに"actions"がありません。')
return
try:
code = generate_rpa_code(data)
except KeyError as e:
print(f"エラー: Actionに必要な項目がありません: {e}")
return
except ValueError as e:
print(f"エラー: {e}")
return
output_path.write_text(code, encoding="utf-8")
print("========================================")
print(" PyAutoGUI Generator")
print("========================================")
print()
print(f"入力 : {input_path}")
print(f"出力 : {output_path}")
print()
print("PyAutoGUIコードを生成しました。")
print("========================================")
if __name__ == "__main__":
main()
4. Actionの種類
現在は以下のActionに対応しています。
| Action | 内容 |
|---|---|
click |
マウスクリック |
double_click |
ダブルクリック |
scroll |
スクロール |
drag |
ドラッグ&ドロップ |
text |
文字入力 |
key |
単一キー |
hotkey |
複数キーの同時押下 |
ime |
IME切り替え |
5. 各Actionの変換詳細
5.1. click
例えば、
{
"type": "click",
"position": {
"x": 500,
"y": 300
},
"button": "left"
}
を pyautogui.click(500, 300, button='left') に変換します。
if action_type == "click":
x = action["position"]["x"]
y = action["position"]["y"]
button = action["button"]
return f"pyautogui.click({x}, {y}, button={quote(button)})"
5.2. double_click
ダブルクリックにも対応しています。
{
"type": "double_click",
"position": {
"x": 500,
"y": 300
},
"button": "left"
}
↓
pyautogui.doubleClick(500, 300, button='left')
5.3. scroll
スクロールの場合は、マウスカーソルを指定位置へ移動してからスクロールします。
ただしdeltaのスクロール移動量は、ソフトによって変わるため、そのままでは期待する移動をしません。そのため手動で移動量を変える必要があります。
{
"type": "scroll",
"position": {
"x": 500,
"y": 300
},
"delta": -1
}
↓
pyautogui.moveTo(500, 300)
pyautogui.scroll(-1)
5.4. drag
ドラッグ操作は、
- 開始位置へ移動
- マウスボタンを押す
- 終了位置へ移動
- マウスボタンを離す
という4段階に変換しています。
{
"type": "drag",
"button": "left",
"start": {
"x": 100,
"y": 200
},
"end": {
"x": 500,
"y": 400
},
"duration": 1.0
}
↓
pyautogui.moveTo(100, 200)
pyautogui.mouseDown(button='left')
pyautogui.moveTo(500, 400, duration=1.0)
pyautogui.mouseUp(button='left')
単純な dragTo() ではなく、mouseDown() → moveTo() → mouseUp() としているのがポイントです。
5.5. text
文字入力は pyautogui.write() に変換します。
{
"type": "text",
"text": "Hello World"
}
↓
pyautogui.write("Hello World")
キーボード操作を1キーずつ保存するのではなく、連続した文字入力を text として扱えるようにしています。
5.6. key
EnterやBackspaceなどの単一キーは key として扱います。
{
"type": "key",
"key": "enter"
}
↓
pyautogui.press('enter')
5.7. hotkey と Windowsキーの対応
複数キーの同時押下には hotkey を使用します。
{
"type": "hotkey",
"keys": [
"ctrl",
"c"
]
}
↓
pyautogui.hotkey('ctrl', 'c')
また、Windowsキーについては、操作記録側で使われる名称とPyAutoGUIで使用する名称が異なる場合があります。そこで、以下の変換表を用意しています。
key_map = {
"left windows": "winleft",
"right windows": "winright",
"windows": "win",
"win": "win",
}
例えば、
{
"type": "hotkey",
"keys": [
"left windows",
"r"
]
}
の場合、pyautogui.hotkey('winleft', 'r') を生成します。
操作記録側のキー名
↓
key_map
↓
PyAutoGUIで使用するキー名
5.8. IME
IME切り替えについては、専用のActionとして用意しています。
{
"type": "ime"
}
↓
pyautogui.press('kanji')(日本語Windows環境でIMEの切り替え操作を記録するためのもの)
5.9. 操作間の delay
各Actionには "delay": 1.0 のような待ち時間を設定できます。
{
"type": "click",
"position": {
"x": 500,
"y": 300
},
"button": "left",
"delay": 2.0
}
↓
time.sleep(2.0)
pyautogui.click(500, 300, button='left')
これにより「クリック → 2秒待つ → 次の操作」というRPAが生成できます。
6. コアロジックの解説
generate_action()
プログラムの中心となるのがこの関数です。Actionの type を確認し、それぞれ対応するPyAutoGUIコードへ変換します。
action["type"]
│
├── click
├── double_click
├── scroll
├── drag
├── text
├── key
├── hotkey
└── ime
↓
PyAutoGUIコード
対応していないActionが渡された場合は、raise ValueError(f"未対応のActionです: {action_type}") としてエラーにします。
generate_rpa_code()
複数のActionをまとめてPythonコードに変換します。ファイルのヘッダー部分を作成した後、各アクションを順次処理して結合します。
エラー処理
- JSONファイルが存在しない場合のエラーチェック
- JSONの形式不正 (
json.JSONDecodeError) のキャッチ - 必要な項目がない場合 (
KeyError) のキャッチ
7. 実行方法
-
actions.jsonとpyauto_generator.pyを同じフォルダに配置します。
project/
├─ actions.json
└─ pyauto_generator.py
- スクリプトを実行します。
python pyauto_generator.py
- 成功すると以下のように表示され、
generated_rpa.pyが生成されます。
========================================
PyAutoGUI Generator
========================================
入力 : actions.json
Output : generated_rpa.py
PyAutoGUIコードを生成しました。
========================================
プロジェクト構成:
project/
├─ actions.json
├─ pyauto_generator.py
└─ generated_rpa.py
8. 全体の構成イメージ
今回のプログラムは単なるコード生成器ですが、最終的には次のような構成を想定しています。
┌─────────────────┐
│ マウス操作 │
│ キーボード操作 │
└────────┬────────┘
↓
┌─────────────────┐
│ 操作レコーダー │
└────────┬────────┘
↓
┌─────────────────┐
│ actions.json │
└────────┬────────┘
↓
┌───────────┴───────────┐
↓ ↓
┌───────────────────┐ ┌─────────────────┐
│PyAutoGUI Generator│ │ 別Generator │
└────────┬──────────┘ └────────┬────────┘
↓ ↓
generated_rpa.py 別のRPAコード
この構成にすることで、操作を記録する部分と、実際に操作を実行するライブラリを綺麗に分離できます。
9. 今後の課題・展望
現時点では基本的な操作をPyAutoGUIコードへ変換できるようになりましたが、今後は以下のような機能拡張を予定しています。
- 画像認識Actionへの対応
- ウィンドウ待機処理
-
pyautogui以外のRPAコード生成 (pywinauto,mouseなど) - JSONのスキーマ検証
操作データそのものを特定のライブラリに依存しない形で設計しておけば、将来的に多様なRPAバックエンドへ展開が可能になります。
まとめ
今回は、操作内容をJSON形式で保存し、そのJSONからPyAutoGUIのコードを自動生成する pyauto_generator.py を作成しました。
- 操作データを共通フォーマット(JSON)として保存する
- JSONと実行ライブラリを分離する
- Actionの種類ごとにコードを綺麗に自動生成する
- マウス・キーボード操作(hotkeyやIME切り替え含む)に対応する
-
delayを使って操作間の待ち時間を再現する
単に「マウス操作をPythonコードに変換する」だけでなく、操作を共通フォーマットで記録し、そこから任意のライブラリ向けコードを生成するというアプローチを取ることで、柔軟性の高いRPA基盤に発展させられそうです。