1日22時間寝たい

技術頑張ってる最中です

pywinautoとopenpyxlでエクセルエビデンススクショマン(意訳)を楽にする

サマリ

はじめに

はてぶ界隈でよく話題になるエクセルエビデンススクショマン(システムが想定の動作をしているという証拠をスクリーンショットで取ってエクセルに貼る仕事をするマン:長いので以下スクショマン)の話を聞きながら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での実装にしました。

公式は以下。

github.com

examplesディレクトリにかなりコードサンプルがあるし、しっかり直近でメンテされてるっぽいので良さげ。 f:id:SHERRY3594:20190318215356p:plain

意外と日本語情報があまりなかったので、一応ざっくりとした実装だけ載せておきます。

ざっくりとした実装

  • 今回の目標は「スクリプトを走らせている間に『1』が押されたら(キーボードアクションを待って)全画面のスクリーンショットを撮って、『2』が押されたら撮ったスクショをエクセルに貼り付けてスクリプトを終了する」だけ。

実装例は以下(exsamplesのhook_and_listen.py)をそのまま参照してきたらOK。

github.com

必要なものをインポート

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
  • クラス化したのは画像のタイトルを若い順に付けたかったから(業務では日付+数字とかにしている)
    • ちなみにこれだと「1.png 3.png 5.png」みたいに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!")

そもそもこのエビデンス作業もうちょっとどうにかならないのだろうか


  1. 「1.png, 10.png, 11.png, … , 2.png, 20.png, … , 3.png, …」となってしまう