PyAutoGUIの入力速度の超高速化

PyAutoGUIの入力速度の超高速化

これは自己責任です

灰文字ではなく明示的に黒文字で、しかもh2で書きます。

あなたの使っているコンピューターが壊れても、私は一切の責任を負いません。このページを参考にしてプログラムを書き換えて、コンピューターをダメにしてもそれは私の責任ではないので、自分で対処してください。

はじめに

面倒なことはPythonにやらせたい!

これはPythonistaならよく思うことだと思います。

キーボード入力でラクするためにPyAutoGUIを使うことも多いはずです。

しかし、PyAutoGUI(以下、pguiと略します)の入力速度は遅い! もっと早くしたい!

そんな人のためのページです笑

結構すぐ読み終わります。

環境

  • macOS Catalina(10.15.5)
  • MacBook Air (13-inch, 2017)
  • Python 3.8.0
  • PyAutoGUI 0.9.48

Windowsでもまあたぶんできますが、実際に私は実行していないので分かりません。

また、私はvimmerなのでIDEのことは知りません。知ってても知りません()

なので、IDEで実際にできるかを調べていないので、Anacondaとかでやってる場合は自分で確認してください。

な、何をするんですか!?

具体的なフローはこんな感じです。

  1. ライブラリーpguiがどこに入っているのか調べる
  2. pguiの中に入っているファイルを書き換える。具体的には、_pyautogui_osx.pyを書き換える(今回はmacOSで実行しているのでこうなります)

手順はめっちゃ少ないです。

1. ライブラリーpguiがどこに入っているのか調べる

import pyautogui as pgui

if __name__ == '__main__':
    prin(f'{pgui.__file__=}')

で出力はこんな感じになりました(適宜伏字にしています)

pgui.__file__='****/Python.framework/Versions/3.8/lib/python3.8/site-packages/pyautogui/__init__.py'

では、出力されたディレクトリーのパス****/Python.framework/Versions/3.8/lib/python3.8/site-packages/pyautoguiを開いてみましょう。

こんな感じで出力されていると思います。

パスによっては、開けないかもしれませんが、そこは適宜chmodするなりして書き換えられるようにしてください。

2. pguiの中に入っているファイルを書き換える

次に、_pyautogui_osx.pyのファイルを開きます。

time.sleep(0.01)と書かれている行が4箇所あるのですが、そのうちの1行をコメントアウトするなり削除するなりして、読み飛ばすように書き換えます。

import time
import sys
...

def _normalKeyEvent(key, upDown):
    assert upDown in ('up', 'down'), "upDown argument must be 'up' or 'down'"

    try:
        if pyautogui.isShiftCharacter(key):
            key_code = keyboardMapping[key.lower()]

            event = Quartz.CGEventCreateKeyboardEvent(None,
                        keyboardMapping['shift'], upDown == 'down')
            Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
            # Tiny sleep to let OS X catch up on us pressing shift
            time.sleep(0.01)

        else:
            key_code = keyboardMapping[key]

        event = Quartz.CGEventCreateKeyboardEvent(None, key_code, upDown == 'down')
        Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
        time.sleep(0.01)

    # TODO - wait, is the shift key's keyup not done?
    # TODO - get rid of this try-except.

    except KeyError:
        raise RuntimeError("Key %s not implemented." % (key))

...

def _dragTo(x, y, button):
    if button == LEFT:
        _sendMouseEvent(Quartz.kCGEventLeftMouseDragged , x, y, Quartz.kCGMouseButtonLeft)
    elif button == MIDDLE:
        _sendMouseEvent(Quartz.kCGEventOtherMouseDragged , x, y, Quartz.kCGMouseButtonCenter)
    elif button == RIGHT:
        _sendMouseEvent(Quartz.kCGEventRightMouseDragged , x, y, Quartz.kCGMouseButtonRight)
    else:
        assert False, "button argument not in ('left', 'middle', 'right')"
    time.sleep(0.01) # needed to allow OS time to catch up.

def _moveTo(x, y):
    _sendMouseEvent(Quartz.kCGEventMouseMoved, x, y, 0)
    time.sleep(0.01) # needed to allow OS time to catch up.

これを……、

import time
import sys

...

def _normalKeyEvent(key, upDown):
    assert upDown in ('up', 'down'), "upDown argument must be 'up' or 'down'"

    try:
        if pyautogui.isShiftCharacter(key):
            key_code = keyboardMapping[key.lower()]

            event = Quartz.CGEventCreateKeyboardEvent(None,
                        keyboardMapping['shift'], upDown == 'down')
            Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
            # Tiny sleep to let OS X catch up on us pressing shift
            time.sleep(0.01)

        else:
            key_code = keyboardMapping[key]

        event = Quartz.CGEventCreateKeyboardEvent(None, key_code, upDown == 'down')
        Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
        #time.sleep(0.01)

    # TODO - wait, is the shift key's keyup not done?
    # TODO - get rid of this try-except.

    except KeyError:
        raise RuntimeError("Key %s not implemented." % (key))

...

def _dragTo(x, y, button):
    if button == LEFT:
        _sendMouseEvent(Quartz.kCGEventLeftMouseDragged , x, y, Quartz.kCGMouseButtonLeft)
    elif button == MIDDLE:
        _sendMouseEvent(Quartz.kCGEventOtherMouseDragged , x, y, Quartz.kCGMouseButtonCenter)
    elif button == RIGHT:
        _sendMouseEvent(Quartz.kCGEventRightMouseDragged , x, y, Quartz.kCGMouseButtonRight)
    else:
        assert False, "button argument not in ('left', 'middle', 'right')"
    time.sleep(0.01) # needed to allow OS time to catch up.

def _moveTo(x, y):
    _sendMouseEvent(Quartz.kCGEventMouseMoved, x, y, 0)
    time.sleep(0.01) # needed to allow OS time to catch up.

こう!

2行目だけをコメントアウトすることを推奨します。

プログラムのコメントアウトにも書いてあるとおり、シフトを使う文字、例えば「?」や「!」に関しては条件分岐しています。

0.01秒はどうやら、シフトを外すために必要な時間だそうです。

ここをコメントアウトして試してみましたが、ロクなことになりませんでした。

で、何をしたの?

実はpguiではtime.sleep(0.01)という処理によって、打速を単位秒あたり100打(以下)しかキーを打てない構造になっているのです。これは驚き。

この部分を削除すると文字通り、入力速度の超高速化が実現できるわけです。

実行例のプログラム

実際に実行してみたところ、800字くらいなら1秒で入力することができました。速すぎて実際にちゃんと実行しているのかも分かりませんでした。

import pyautogui as pgui
from time import sleep


if __name__ == '__main__':

    print('プログラムを5秒待ってから実行します')
    sleep(5)
    pgui.write('abcdefgh' * 100)

さいごに

今回はpguiの超高速化について考えました。

1秒に100打よりも圧倒的に速い入力を実現できたと思います。

ただし自己責任です。