본문 바로가기
Python

python을 통한 단순 작업 자동화 구현 (RPA의 시작?)

by 청양호박이 2021. 5. 14.

이번에는 python을 통해서 자동화를 구현하는 작업을 수행해 보겠습니다. 일상적에서 반복적으로 수행되는 작업 중 단순하게 수행이 가능한 것들은 매번 사람이 하기에는 너무 비효율적 입니다. 중간에 사람의 논리적인 판단이 들어가거나 하지를 않는다면 더더욱 그렇겠죠.

 

최근에는 RPA라고 해서 Robotic Process Automation(로봇 프로세스 자동화)로 단순한 작업에 대해서 컴퓨팅 파워로 빠르고 비용에 효율적으로 수행하게 됩니다. python을 통해서 구현하는 자동화도 그 RPA에 대한 시작이라고 생각해도 되지 않을까 싶습니다. 

 

이렇게 반복적인 normal한 작업들은 프로그램을 만들어서 자동화를 할 수 있습니다. 많은 언어들이 이를 가능하게 하겠지만, 간단하고 편하게 구현하기 위해서 python으로 반복작업에 대한 자동화를 구현해 보도록 하겠습니다. 

자동화의 기준은 보통 사용자들이 업무를 할때 사용하는 Microsoft Windows를 기준으로 하겠습니다. 이렇게 OS가 정의되지 않으면, 아래의 방법을 통해서 Linux에서도?? MAC OS에서도?? 될 거라는 생각을 할 수 있기 때문입니다.

 

1. What is pywinauto?


pywinauto가 위에서 말한 MS Windows GUI환경에서 python을 통해서 작업의 자동화를 가능하게 해주는 package 입니다. 이를 통해서 원하는 application에 원하는 keyboard혹은 mouse 액션을 컨트롤하게 됩니다. 해당 package는 npm repository에서 검색이 되지는 않으며, 공식 docs로 확인이 가능합니다. 


https://pywinauto.readthedocs.io/

 

What is pywinauto — pywinauto 0.6.8 documentation

© Copyright 2018, Mark Mc Mahon and Contributors Revision aea0429b.

pywinauto.readthedocs.io

이렇게 python을 통해서 MS환경내 application을 컨트롤하도록 도와주는 package는 여러개 있습니다. PyAutoGui, winGuiAuto, AXUI 등이 있다고 합니다. 하지만 저는 일단 pywinauto를 사용해서 구현해 보겠습니다.

 

 

2. pywinauto install


저는 conda에서 virtual env를 구성하여 작업하기 때문에 해당 방식으로 보여드리겠습니다. 우선 anaconda prompt를 실행합니다. 실행이 되면 내가 원하는 virtual env를 activate해줍니다. 

 

기존에 제가 관리하던 virtual env가 기억이 안나지만 당황하지 않고 "conda info -e" 명령을 통해서 확인해 줍니다.

(base) C:\Users>conda info -e
# conda environments:
#
base                  *  C:\Users\anaconda3
AT_32                    C:\Users\anaconda3\envs\AT_32


(base) C:\Users>conda activate AT_32

(AT_32) C:\Users>pip install pywinauto
Collecting pywinauto
  Downloading pywinauto-0.6.8-py2.py3-none-any.whl (362 kB)
     |████████████████████████████████| 362 kB 726 kB/s
Collecting comtypes
  Downloading comtypes-1.1.10.tar.gz (145 kB)
     |████████████████████████████████| 145 kB 1.3 MB/s
Collecting pywin32
  Downloading pywin32-300-cp37-cp37m-win32.whl (8.5 MB)
     |████████████████████████████████| 8.5 MB 262 kB/s
Requirement already satisfied: six in c:\users\anaconda3\envs\at_32\lib\site
-packages (from pywinauto) (1.15.0)
Building wheels for collected packages: comtypes
  Building wheel for comtypes (setup.py) ... done
  Created wheel for comtypes: filename=comtypes-1.1.10-py3-none-any.whl size=164
923 sha256=4dae0475c8dad88e3831bbd2fc82a565807900f263196086085307d4be3b1333
  Stored in directory: c:\users\appdata\local\pip\cache\wheels\ac\d9\86\f150
377841ceef17cc9831ead2989c241b39e973269b347653
Successfully built comtypes
Installing collected packages: comtypes, pywin32, pywinauto
Successfully installed comtypes-1.1.10 pywin32-300 pywinauto-0.6.8

(AT_32) C:\Users>

정상적으로 설치가 되었습니다. 그렇다면 슬슬 코딩을 시작해 볼까요??

 



 

3. Windows GUI 확인


pywinauto를 통해서 Windows내 GUI application을 컨트롤하기 위해서는 해당 application이 어떤 녀석인지 파악해야 합니다. Windows에서 지원하는 방식은 2가지 입니다. 

 

  1.  Win32 API - MFC, VB6등과 같이 오래된 레가시 application들이 해당 (backend="win32")
  2.  MS UI Automation - Store apps, Qt5, browsers와 같은 application들이 해당 (backend="uia")

 

이를 파악하기 위해서 tools가 제공됩니다. Spy++, Inspect.exe, py_inspect등이 있다고 합니다. 저는 이 중에서 pywinauto를 기반으로한 py_inspect를 사용해 보도록 하겠습니다. 이 tool은 기존에 SWAPY라고 불리는 프로그램이였고, pywinauto의 버전이 0.5.4부터 없어졌습니다. 현재 설치된 pywinauto의 버전은 0.6.8이고 py_inspect를 사용하면 됩니다. 

 

 

4. py_inspect


py_inspect tool은 GitHub에서 공식적으로 제공합니다. 따라서 아래의 주소로 접근하시고 해당 소스를 받아서 사용하시면 됩니다.
https://github.com/pywinauto/py_inspect 내 py_inspect.py파일입니다.

 

이를 사용하기 위해서는 주의사항이 있습니다. 우선 Windows OS에서 실행해야 합니다. 이때, win10을 추천한다고 하고 ... 실질적으로 필요한 녀석들은 python 3.5자체와 관련 package인 pywinauto, PyQt5가 install되어 있어야 합니다.

 

그럼 실행을 해 볼까요??

위에 보시는 것처럼 바탕화면을 기준으로 현재 실행되어있는 프로그램에 대한 정보가 보여지게 됩니다. 해당 그림에서 주의깊게 봐야할 부분은 [Pane "바탕 화면" > Window "계산기"] 이 부분입니다. 앞으로 이 계산기에 대해서 자동화를 예시로 구현해 볼 것이기 때문입니다. 

 

이 "계산기"는 현재 Window시스템에서 py_inspect로 확인한 프로그램명 입니다. py_inspect로 활용할 부분은 일단 이정도라고 생각해주시면 됩니다. 그럼 다음 단계로 넘어가겠습니다. 

 

 

5. print_control_identifiers를 통한 application세부정보 알기


실제로 원하는 application을 컨트롤하기 위해서는 해당 application의 세부 정보를 알아야 합니다. 예를들면, button은 어떤것이 있고, 해당 버튼을 지칭하는 id는 무엇이고... 등등의 정보들입니다. 이를 알기위해서 pywinauto에서는 메서드를 제공합니다. 

 

print_control_identifiers( )가 그것입니다. 지금까지 알아봤던 내용을 기준으로 application를 실행시키고 그 세부정보를 알아봐 보도록 하겠습니다. 우선 쉽게 계산기로 진행해 보겠습니다. 

from pywinauto import application

app = application.Application(backend='uia').start('calc.exe')
dlg_spec = app.window(title='계산기')
dlg_spec.print_control_identifiers()

Application으로 backend는 UI Automation이고 start메서드에 실행하고자 하는 프로그램의 경로와 프로그램명을 모두 입력해 줍니다. 이 코드를 통해서 calc.exe를 실행해 줍니다. 이후로 window메서드에 py_inspect로 확인한 title을 입력해서 calc.exe프로세스 내 window정보를 명시해 줍니다. 

 

명시한 정보를 기준으로 세부정보를 요청하면... 아래와 같이 각 버튼 및 TitleBar, Menu등에 대한 모든 정보를 리턴해 줍니다. 

Dialog - '계산기'    (L462, T443, R690, B765)
['계산기Dialog', '계산기', 'Dialog']
child_window(title="계산기", control_type="Window")
   | 
   | Pane - ''    (L470, T493, R682, B757)
   | ['실행 기록Pane', 'Pane']
   |    | 
   |    | Image - ''    (L481, T503, R671, B553)
   |    | ['실행 기록Image', 'Image']
   |    | child_window(auto_id="160", control_type="Image")
   |    | 
   |    | Static - '결과'    (L499, T518, R666, B546)
   |    | ['결과', '결과Static', 'Static3']
   |    | child_window(title="결과", auto_id="150", control_type="Text")
   |    | 
   |    | Static - '0'    (L499, T521, R666, B551)
   |    | ['0Static', '0', 'Static4', '00', '01']
   |    | child_window(title="0", auto_id="158", control_type="Text")
   |    | 
   |    | Button - '7'    (L481, T622, R515, B649)
   |    | ['7', 'Button3', '7Button']
   |    | child_window(title="7", auto_id="137", control_type="Button")
   |    | 
   |    | Button - '4'    (L481, T654, R515, B681)
   |    | ['Button4', '4Button', '4']
   |    | child_window(title="4", auto_id="134", control_type="Button")
   |    | 
   |    | Button - '1'    (L481, T686, R515, B713)
   |    | ['Button5', '1Button', '1']
   |    | child_window(title="1", auto_id="131", control_type="Button")
   |    | 
   |    | ------------생    략--------------
   | 
   | TitleBar - ''    (L486, T446, R682, B473)
   | ['TitleBar']
   |    | 
   |    | Menu - '시스템'    (L470, T451, R491, B472)
   |    | ['시스템Menu', '시스템', 'Menu', '시스템0', '시스템1', 'Menu0', 'Menu1']
   |    | child_window(title="시스템", auto_id="MenuBar", control_type="MenuBar")
   |    |    | 
   |    |    | MenuItem - '시스템'    (L470, T451, R491, B472)
   |    |    | ['시스템MenuItem', 'MenuItem', '시스템2', 'MenuItem0', 'MenuItem1']
   |    |    | child_window(title="시스템", control_type="MenuItem")
   |    | 
   |    | Button - '최소화'    (L579, T444, R608, B464)
   |    | ['Button29', '최소화Button', '최소화']
   |    | child_window(title="최소화", control_type="Button")
   |    | 
   |    | Button - '최대화'    (L608, T444, R635, B464)
   |    | ['Button30', '최대화', '최대화Button']
   |    | child_window(title="최대화", control_type="Button")
   |    | 
   |    | Button - '닫기'    (L635, T444, R684, B464)
   |    | ['닫기', 'Button31', '닫기Button']
   |    | child_window(title="닫기", control_type="Button")
   | 
   | Menu - '응용 프로그램'    (L470, T473, R682, B492)
   | ['응용 프로그램', '응용 프로그램Menu', 'Menu2']
   | child_window(title="응용 프로그램", auto_id="MenuBar", control_type="MenuBar")
   |    | 
   |    | MenuItem - '보기(V)'    (L470, T473, R524, B492)
   |    | ['MenuItem2', '보기(V)', '보기(V)MenuItem']
   |    | child_window(title="보기(V)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '편집(E)'    (L524, T473, R576, B492)
   |    | ['MenuItem3', '편집(E)', '편집(E)MenuItem']
   |    | child_window(title="편집(E)", control_type="MenuItem")
   |    | 
   |    | MenuItem - '도움말(H)'    (L576, T473, R643, B492)
   |    | ['도움말(H)', 'MenuItem4', '도움말(H)MenuItem']
   |    | child_window(title="도움말(H)", control_type="MenuItem")

이렇게 세부정보를 보게 되었다면, pywinauto를 통한 application의 자동화 준비가 모두 끝났습니다. 

 



 

6. pywinauto를 통한 계산기 자동화 구현


자 이제 계산기를 통해서 자동으로 계산하는 간단한 로직을 구현해 보겠습니다. 전체적인 Flow는 아래와 같습니다. 아마 이 정도를 구현해 본다면 나머지는 이거저거 찾아보면서 구현이 가능할 것으로 보입니다.

 

  1. 계산기(calc.exe) 프로그램을 실행
  2. 프로그램의 MenuItem을 기준으로 메뉴를 선택하여 변경하기 - [보기 > 공학용]
  3. 계산기의 버튼을 눌러서 더하기를 수행하기 - [7 + 8 = 15]
  4. 결과가 저장되는 Text child_window에서 결과값 즉 value를 가져오기 - get result value from text control_type
  5. 가져온 결과값을 출력하고 창을 최소화하기

 

이 절차를 요약한 코드는 아래와 같습니다.

from pywinauto import application

# calc.exe 실행하기
app = application.Application(backend='uia').start('calc.exe')
dlg_spec = app.window(title='계산기')

# control_type이 MenuItem일 경우 이동하는 방법
dlg_spec.menu_select("보기(V) -> 공학용(S)")

# control_type이 Button일 경우 동작시키는 방법
dlg_spec.child_window(title="7", auto_id="137", control_type="Button").click()
dlg_spec.child_window(title="더하기", auto_id="93", control_type="Button").click()
dlg_spec.child_window(title="8", auto_id="138", control_type="Button").click()
dlg_spec.child_window(title="같음", auto_id="121", control_type="Button").click()

# control_type이 Text일 경우 결과값을 가져오는 방법
print(dlg_spec.child_window(title="결과", auto_id="150", control_type="Text").legacy_properties())
a = dlg_spec.child_window(title="결과", auto_id="150", control_type="Text").legacy_properties()['Value']
print(a)

# window 최소화 하기
dlg_spec.minimize()

세부적인 내용은 주석을 참조하시면 이해가 되실 것이라 생각됩니다. 추가적으로 결과값을 python변수로 가져오는 방법이 있는데, 해당 text control_type의 child_window를 정의하고 legacy_properties( )를 호출하면 아래와 같이 해당 child_window에 대한 정보를 알려줍니다. 

{'ChildId': 0, 'DefaultAction': '', 'Description': '', 'Help': '', 'KeyboardShortcut': '', 'Name': '결과', 'Role': 41, 'State': 64, 'Value': '15 '}

이 정보중에 느낌이 오는 값은 Value로 해당 부분을 추출하면 됩니다. 그 결과로 아래와 같이 계산기가 정상적으로 더하기 작업을 수행하고 해당결과를 동일하게 python변수에 담아서 print가 됩니다.

 

[결과]

{'ChildId': 0, 'DefaultAction': '', 'Description': '', 'Help': '', 'KeyboardShortcut': '', 'Name': '결과', 'Role': 41, 'State': 64, 'Value': '15 '}
15 

계산기가 공학용으로 바뀌었으며, 더하기 결과인 15가 보여집니다. 또한, python의 결과로 15가 동일하게 print되었습니다. 어떠신가요... 이제 단순 반복되는 작업을 자동화할 준비가 되셨습니다. 필요한 부분을 실제로 구현해 보면 실 생활에 도움이 되지 않을까 생각됩니다.  

 

- Ayotera Lab -

댓글