pywinautoとopenpyxlでエクセルエビデンススクショマン(意訳)を楽にする
サマリ
- pywinautoとopenpyxlで、スクリーンショットを延々とエクセルに貼る仕事の一部を自動化する
- 具体的には「スクリプトを走らせている間に『1』が押されたら(キーボードアクションを待って)全画面のスクリーンショットを撮って、『2』が押されたら撮ったスクショをエクセルに貼り付けてスクリプトを終了する」なんちゃってスクリプトを書く
はじめに
はてぶ界隈でよく話題になるエクセルエビデンススクショマン(システムが想定の動作をしているという証拠をスクリーンショットで取ってエクセルに貼る仕事をするマン:長いので以下スクショマン)の話を聞きながらSIerは大変だな等と思っていた時期もありました。
だがしかし、ネットワーク屋さんにもスクショマンがいた。
大体のFirewallの設定はポータルサイト(GUI)からだし、無線APの設定も大抵ポータルからだし(GUI)、マネージド系企業用ルータにもポータルサイト(GUI)から設定するものが増えてきた気がする。セキュリティの観点からTelnet禁止とか厳しすぎやん……
しかもインテグレーション系が主戦場のじゃぱにーずとらでぃしょなるかんぱにーではほぼWindows。PrintScreenして、ペイントに貼って、トリミングしてから、エクセルに貼る(他のいいやり方を知らない)。しかも、JISキーボード。
つまり、スクショマンは結構辛い。辛いが、一応仕事なのでやるしかない。しかし、どうにか雑にでも手間を減らしたい。
以上が今回の趣旨です。
使うもの
タイトルから連呼している通り以下の2つ。
pywinauto
openpyxl
日本語で雑に「Python GUI 操作」みたいにググった結果pyhooked
というライブラリがヒットしたのでそれを使おうかと思ったものの、
ATTENTION: pyhooked has been deprecated in favor of keyboard for new projects. This library is will no longer be supported. Please use one keyboard or one of the alternatives listed in Alternatives. からの pyHook and pyhk inspired the creation of this project. They are great hotkey modules too! pywinauto is an incredibly useful Windows automation library that also includes among a plethora of tools, a hotkey detection library.
とあるので、代替としておすすめされている通りpywinauto
での実装にしました。
公式は以下。
examplesディレクトリにかなりコードサンプルがあるし、しっかり直近でメンテされてるっぽいので良さげ。
意外と日本語情報があまりなかったので、一応ざっくりとした実装だけ載せておきます。
ざっくりとした実装
- 今回の目標は「スクリプトを走らせている間に『1』が押されたら(キーボードアクションを待って)全画面のスクリーンショットを撮って、『2』が押されたら撮ったスクショをエクセルに貼り付けてスクリプトを終了する」だけ。
実装例は以下(exsamplesのhook_and_listen.py)をそのまま参照してきたらOK。
必要なものをインポート
from pywinauto.win32_hooks import Hook, KeyboardEvent from PIL import ImageGrab import openpyxl, time, sys, glob, re
スクリーンショットをとる部分と終了する部分
class C: def __init__(self): self.c = 1 def on_event(self, args): if isinstance(args, KeyboardEvent): if args.current_key == "1" and args.event_type == 'key down': print("1 was pressed") ImageGrab.grab().save(str(self.c) + ".png", quality=100) if args.current_key == "2" and args.event_type == 'key down': png_list = glob.glob("*.png") png_list = sorted(png_list, key=lambda s: int(re.search("\d+", s).group())) save_png(png_list) print("2 was pressed") sys.exit() self.c += 1
- クラス化したのは画像のタイトルを若い順に付けたかったから(業務では日付+数字とかにしている)
ImageGrab.grab().save()
の部分できちんとquality
を指定しないとだいぶ解像度低いスクショになるので注意- 『2』が押されたときに
png_list
をソートしているのは、そのままだと辞書順になってしまう1ので、数字順にするためエクセルにスクショを貼る部分
def save_png(png_list): wb = openpyxl.Workbook() ws = wb.worksheets[0] i = 1 for png in png_list: img = openpyxl.drawing.image.Image(png) ws.add_image(img, "A" + str(i)) i += 10 wb.save('sample.xlsx')
- 同じ階層に溜まった
.png
ファイルをエクセルに貼るだけ(何か言われたときのためにオリジナル画像はあったほうがいいという日和った心からこういう実装にした)- ここでは適当に
A
の列に10ずつずらして貼っているだけなのでエクセルフォーマットとかで調整するといいと思う
- ここでは適当に
使い方
- キーボードアクションを待って処理をするという部分と、エクセルに画像を貼るという部分だけを書いたけど、あとは成果物としてお客様に提出するフォーマットに合わせて色々変えたらいいと思う
- フォーマットがお客様によって違うのでどこまでをスクリプトに書くか、関数化するか、逆に手作業にするかが悩みどころ
スクリプト全体
from pywinauto.win32_hooks import Hook, KeyboardEvent from PIL import ImageGrab import openpyxl, time, sys, glob, re def save_png(png_list): wb = openpyxl.Workbook() ws = wb.worksheets[0] i = 1 for png in png_list: img = openpyxl.drawing.image.Image(png) ws.add_image(img, "A" + str(i)) i += 10 wb.save('sample.xlsx') class C: def __init__(self): self.c = 1 def on_event(self, args): if isinstance(args, KeyboardEvent): if args.current_key == "1" and args.event_type == 'key down': print("1 was pressed") ImageGrab.grab().save(str(self.c) + ".png", quality=100) if args.current_key == "2" and args.event_type == 'key down': png_list = glob.glob("*.png") png_list = sorted(png_list, key=lambda s: int(re.search("\d+", s).group())) save_png(png_list) print("2 was pressed") sys.exit() self.c += 1 hk = Hook() c = C() print("1:screenshot\n2:end(-->excel)\n※注意 フォルダ内にファイルを置かないように") try: hk.handler = c.on_event time.sleep(0.5) hk.hook(keyboard=True, mouse=True) except: print("Error!")
そもそもこのエビデンス作業もうちょっとどうにかならないのだろうか