본문 바로가기
코딩 연습/코딩배우기

[Python] 파이썬을 활용한 업무자동화 - 데스크탑 자동화(pyautogui - 파이썬으로 마우스, 키보드 제어하기) 연습 코드 정리

by good4me 2021. 7. 6.

goodthings4me.tistory.com

수년 전 'Automatic Mouse and keyboard'라는 프로그램으로 웹 프로그램 테스트 업무에 활용했었다. 파이썬으로도 마우스와 키보드를 제어할 수 있는 라이브러리(pyautogui)가 있다는 것을 알게 되었는데, 업무 자동화 무료 강의를 보고 나중에 참고 자료로 활용하고자 정리했다.

 

 

파이썬을 활용한 업무자동화 - pyautogui로 마우스, 키보드 제어하기

 

목 차

  • 윈도우 & 마우스 위치 이동
  • 마우스 액션
  • 스크린 샷 & pixel 좌표로 색상 값 얻기
  • 이미지 인식 처리 - 기본
  • 찾는 이미지 영역이 2개 이상 발견된 경우
  • 이미지 처리 속도 개선
  • 자동화 대상이 바로 보여지지 않는 경우
  • 윈도우(창) 다루기
  • 키보드 다루기
  • 메시지 박스
  • 자동화 로그 남기는 법

 

※ 라이브러리 설치 : pip install pyautogui
* window에서 제공하는 object 정보를 사용하는 것이 아니라 이미지 기반으로 진행하는 것임

 

■ 윈도우 & 마우스 위치 이동

import pyautogui

size = pyautogui.size()  # 현재 화면의 스크린 사이즈(가로, 세로)를 가져옴
print(size)  # Size(width=1920, height=1080) 
# size[0] : width
# size[1] : height

# 절대 좌표로 마우스 이동
pyautogui.moveTo(700, 200)  # 지정한 위치(가로 x, 세로 y)로 마우스를 이동
pyautogui.moveTo(100, 500, duration=1.5)  # 1.5초 동안 100, 200 위치로 이동

# 상대 좌표로 마우스 이동(현재 커서가 있는 위치로부터) - move()
pyautogui.moveTo(100, 100, duration=1.5)
print(pyautogui.position())  # Point(x, y) 현재 위치
pyautogui.move(100, 100, duration=1.5)  # 100, 100 기준으로부터 +100, +100으로 이동
print(pyautogui.position())  # Point(x, y)
pyautogui.move(100, 100, duration=1.5)  # 200, 200 기준으로부터 +100, +100으로 이동
print(pyautogui.position())  # Point(x, y)

p = pyautogui.position()
print(p[0], p[1])  # x, y
print(p.x, p.y)  # x, y 


[결과]
Size(width=1920, height=1080)
Point(x=100, y=100)
Point(x=200, y=200)
Point(x=300, y=300)
300 300
300 300

 

■ 마우스 액션

import pyautogui

pyautogui.sleep(2)  # 2초 대기
print(pyautogui.position())  # 마우스 현재 위치 Point(x=, y=)


## click() = mouseDown() + mouseUp()
pyautogui.click(19, 44, duration=1)  # (19, 44) 좌표를 마우스 클릭
pyautogui.click(clicks=10)  # 10번 클릭
pyautogui.mouseDown()
pyautogui.mouseUp()
pyautogui.doubleClick()  # pyautogui.click(clicks=2)와 같음


## mouseDown/Up, 그림판 등에서 선 긋기 등에 활용
pyautogui.moveTo(1160, 290)
pyautogui.mouseDown()
pyautogui.moveTo(1300, 400)
pyautogui.mouseUp()


## 마우스 우클릭
pyautogui.rightClick()  # 마우스 우클릭
pyautogui.middleClick()  # 마우스 휠


## 마우스 드래그 - 예로, 화면에 있는 창 클릭 후 이동
pyautogui.sleep(2)
print(pyautogui.position())
pyautogui.moveTo(975, 240)
pyautogui.drag(100, 0, duration=0.25)  # 현재 위치 기준으로 x=100, y=0 만큼 드래그
# 너무 빠른 동작으로 drag 수행이 안될 때는 duration 값을 설정하고 실행
pyautogui.dragTo(1200, 240, duration=0.25)  # 절대 좌표 기준으로 드래그


## 마우스 스크롤
pyautogui.scroll(500)  # 양수이면 위 방향으로 500만큼 스크롤
pyautogui.scroll(-300)  # 음수이면 아래 방향으로 300 스크롤


## 마우스 정보
pyautogui.mouseInfo()  # MouseInfo 프로그램 창 팝업
# 위치 정보를 얻을 곳에 마우스 포인트 위치 후 F1 클릭 시 
# 19,44 229,243,255 #E5F3FF 정보(XY Position, RGB Color, RGB as Hex) 얻어짐


## 자동화 실행 실패 시 중지하기 - 화면 가장자리 모서리에 마우스 위치
# 단, pyautogui.FAILSAFE = False 시는 중지 안함 - 가급적 사용 자제
pyautogui.FAILSAFE = True  # Default는 True
for i in range(10):
    pyautogui.move(100, 100)
    pyautogui.sleep(1.5)


## 모든 동작에 수 초씩 sleep 적용
pyautogui.PAUSE = 1  # 1초, .sleep(1) 안해도 됨 
for i in range(5):
    pyautogui.move(100, 100)

 

■ 스크린 샷 & pixel 좌표로 색상 값 얻기

import pyautogui

## 현재 화면 스크린 샷 후 저장
img = pyautogui.screenshot()
img.save('./image/screenshot_1.png')  # 파일로 저장


## 마우스 위치 픽셀의 좌표에 대한 색상을 반환 - pixelMatchesColor()
# --> 이 지정 색과 일치하는 경우, 특정 기능 수행하도록 할 수 있다.

# 좌상단 spyder icon 픽셀을 pyautogui.mouseInfo()로 확인 11,12 217,8,8 #D90808
pixel = pyautogui.pixel(11, 12)
print(pixel)  # (217, 8, 8) 일치함 확인

print(pyautogui.pixelMatchesColor(11, 12, (217, 8, 8)))  # True
print(pyautogui.pixelMatchesColor(11, 12, pixel))  # True
print(pyautogui.pixelMatchesColor(11, 12, (217, 8, 9)))  # False

 

■ 이미지 인식 처리 - 기본

  • pyautogui로 window 자동화를 할 때는 자동화 대상이 되는 영역을 이미지로 만들고 그 이미지를 전체 또는 지정된 화면 내에서 찾아 그 값을 반환해주는 방식으로 동작함
  • 이미지가 없거나 변경되어 발견 못할 경우, None 출력
  • pyautogui는 이미지 기반이기 때문에 화면 해상도가 바뀌거나 이미지가 약간이라도 바뀌면 실패율이 높음
  • 이미지 만들기 : winKey + Shift + s 후 마무스로 영역 드래그하여 이미지 만듦
  • 영역 캡처 후 그림판에 붙여넣기 후 잘라서 파일로 저장(.png) - arrow_icon.png

good4me.co.kr


import pyautogui

arrow_icon = pyautogui.locateOnScreen('./image/arrow_icon.png')  
# 파일명은 winkey + shitf + s 로 만든 이미지명
print(arrow_icon)  # Box(left=1283, top=89, width=22, height=18)
pyautogui.click(arrow_icon)  # 영역 찾아서 클릭

file_history = pyautogui.locateOnScreen('./image/file_history.png')
print(file_history)  # Box(left=1643, top=994, width=55, height=19)
pyautogui.click(file_history)

 

■ 찾는 이미지 영역이 2개 이상 발견된 경우

(예로 체크박스 여러 개), locateAllOnScreen() 사용

import pyautogui

for i in pyautogui.locateAllOnScreen('./image/image_icon.png'):
    print(i)
    pyautogui.click(i, duration=0.5)

# Box(left=1336, top=167, width=22, height=25)
# Box(left=1336, top=191, width=22, height=25)
# Box(left=1336, top=215, width=22, height=25)
# Box(left=1336, top=239, width=22, height=25)
# Box(left=1336, top=263, width=22, height=25)
# Box(left=1336, top=287, width=22, height=25)
# Box(left=1336, top=311, width=22, height=25)
# Box(left=1336, top=335, width=22, height=25)
# Box(left=1336, top=359, width=22, height=25)
# Box(left=1336, top=383, width=22, height=25)

 

■ 이미지 처리 속도 개선

- grayscale=True, region=(), confidence=0.999

import pyautogui

# 1. GrayScale - 흑백으로 전환해서 비교 처리 - argument로 grayscale=True
file_history = pyautogui.locateOnScreen('./image/file_history.png', grayscale=True)
print(file_history)  # Box(left=1643, top=994, width=55, height=19)
pyautogui.click(file_history)

# 2. 범위 지정 - region=()
pyautogui.mouseInfo()  # argument로 region=(x, y, width, height) 1485, 981, 1738, 1023
file_history = pyautogui.locateOnScreen('./image/file_history.png', region=(1485, 918, 1738-1458, 1023-981))
print(file_history)  # Box(left=1643, top=994, width=55, height=19)
pyautogui.click(file_history)

# 3. 정확도 조정 - 자동화 대상 영역 이미지의 해상도 줄이고 몇 % 이상이면 인식
# 모듈 설치 pip install opencv-python 그리고 argument로 confidence=0.999 (디폴트 값, 99.9%)
file_menu = pyautogui.locateOnScreen('./image/file_menu.png', confidence=0.7) # confidence 조정하면서 테스트
print(file_menu)  # Box(left=1, top=31, width=39, height=22)
pyautogui.click(file_menu)

 

■ 자동화 대상이 바로 보여지지 않는 경우

(프로그램 실행 또는 버튼 클릭했을 때 약간의 시간이 필요한 경우)

import pyautogui
import time

## 1. 계속 기다리기
# 예시) 메모장 메뉴(파일)를 자동화 대상으로 하고 메모장을 닫은 후, 프로그램 실행 중 메모장을 띄웠을 때 자동화 실행되도록 하기
# 메모장을 안띄워서 발견 못하기 때문에 'None'이 반환됨을 이용 - while 문으로 계속 체크 중 메모장 띄우면 while문 탈출

while pyautogui.locateOnScreen('./image/file_menu_notepad.png') is None:
    file_menu_notepad = pyautogui.locateOnScreen('./image/file_menu_notepad.png')
    print(file_menu_notepad)
    
pyautogui.click(file_menu_notepad)


## 2. 일정 시간 동안 기다리기 (TimeOut)
timeout = 10  # 10초 대기
start = time.time()  # 시작 시간 설정
file_menu_notepad = None

while file_menu_notepad is None:
    file_menu_notepad = pyautogui.locateOnScreen('./image/file_menu_notepad.png')
    end = time.time()  # 종료 시간 설정
    print(file_menu_notepad)
    if end - start > timeout:  # 지정한 10초를 초과하면
        print('TimeOut')
        break

pyautogui.click(file_menu_notepad)


## 함수로 만들어보기
def find_target(img_file, timeout):
    start = time.time()  # 시작 시간 설정
    target = None
    while target is None:
        target = pyautogui.locateOnScreen(img_file)
        end = time.time()  # 종료 시간 설정
        print(target)
        if end - start > timeout:
            break
    return target


def my_click(img_file, timeout=30):
    target = find_target(img_file, timeout)
    if target:
        pyautogui.click(target)
    else:
        print(f'[TimeOut {timeout}s] Target not found ({img_file}). Terminate program.')
        sys.exit()

my_click('./image/file_menu_notepad.png', 10)

 

 

■ 윈도우(창) 다루기

## windows에서 응프로그램(계산기 등) 실행 시, 그 응프로그램의 위치 값을 받아올 수 있음
fw = pyautogui.getActiveWindow()  # 현재 활성화된 윈도우(forground window), 즉 응용프로그램 실행 창
print(fw.title)  # 실행 창의  제목 정보, Spyder (Python 3.8),
print(fw.size)  # 실행 창의 크기 정보(width, height), Size(width=1938, height=1060)
print(fw.left, fw.top, fw.right, fw.bottom)  # 실행 창의 좌표 정보, -9 -9 1929 1051
pyautogui.click(fw.left + 25, fw.top + 20)  # 실행 창을 줄이거나 이동해도 해당 위치에서 마우스 클릭이 됨

fwAll = pyautogui.getAllWindows()  # 모든 윈도우 가져오기
for w in fwAll:
    print(w)

# <Win32Window left="2390", top="-10", width="2420", height="1370", title="Daum - Chrome">
# <Win32Window left="2390", top="-10", width="2420", height="1370", title="NAVER - Chrome">
# <Win32Window left="-9", top="-9", width="1938", height="1060", title="D:\backup\[_강의]">
# ...


## 어떤 제목을 가지는 윈도우(창) 정보 가져오기
windows = pyautogui.getWindowsWithTitle('Chrome')  # list로 반환
print(type(windows))  # <class 'list'>
print(windows)  # [Win32Window(hWnd=3345288), Win32Window(hWnd=67414)]
print(windows[0])
# <Win32Window left="2390", top="-10", width="2420", height="1370", title="Daum - Chrome">

for w in windows:
    print(w)

# <Win32Window left="2390", top="-10", width="2420", height="1370", title="Daum - Chrome">
# <Win32Window left="2390", top="-10", width="2420", height="1370", title="NAVER - Chrome">


w = pyautogui.getWindowsWithTitle('메모장')
print(w)
# [Win32Window(hWnd=591528), Win32Window(hWnd=329480)]

print(w[0])
# <Win32Window left="3380", top="370", width="941", height="793", title="메모1 - Windows 메모장">

w = pyautogui.getWindowsWithTitle('메모장')[0]
if w.isActive == False:  # 현재 활성화되지 않았다면
    pywinauto.application.Application().connect(handle=w._hWnd).top_window().set_focus()
    w.activate()  # PyGetWindowException: Error code from Windows: 0 - 작업을 완료했습니다.
    
if w.isMaximized == False:  # 최대화되지 않았다면
    w.maximize()

pyautogui.sleep(1)

if w.isMinimized == False:
    w.minimize()

pyautogui.sleep(1)

w.restore()  # 화면 원복 (직전 상태)

 

■ 키보드 다루기

import pyautogui
import pywinauto
import pyperclip

w = pyautogui.getWindowsWithTitle('메모장')[0]
pywinauto.application.Application().connect(handle=w._hWnd).top_window().set_focus()

pyautogui.write('12345 나도코딩')  # pyautogui는 영문, 숫자만 가능
pyautogui.write('NadoCoding', interval=0.25)  # write 속도 조정


## 방향키, 기능키 등과 같이 쓰는 법 - list 형태로 넣기
pyautogui.write(['enter', 't', 'e', 's', 't', 'left', 'right', 'backspace', 'l', 'a', 'enter'], interval=0.25)
# enter  t e s t 적고 왼쪽 방향키 1번, 오른쪽 방향키 1번, 백스페이스 1번 l a 적고 enter -- 키조작
# 'left', 'right' 등은 구글 automate the boring stuff with python 검색
# - https://automatetheboringstuff.com/
# - Chapter 20 – Controlling the Keyboard and Mouse with GUI Automation

#################################################################################
# PyKeyboard Attributes
# Keyboard key string  /  Meaning
# 'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '!', '@', '#'  / The keys for single characters
# 'enter' (or 'return' or '\n')  /  The ENTER key
# 'esc'  /  The ESC key
# 'shiftleft', 'shiftright'  /  The left and right SHIFT keys
# 'altleft', 'altright'  /  The left and right ALT keys
# 'ctrlleft', 'ctrlright'  /  The left and right CTRL keys
# 'tab' (or '\t')  /  The TAB key
# 'backspace', 'delete'  /  The BACKSPACE and DELETE keys
# 'pageup', 'pagedown'  /  The PAGE UP and PAGE DOWN keys
# 'home', 'end'  /  The HOME and END keys
# 'up', 'down', 'left', 'right'  /  The up, down, left, and right arrow keys
# 'f1', 'f2', 'f3', and so on  /  The F1 to F12 keys
# 'pause'  /  The PAUSE key
# 'capslock', 'numlock', 'scrolllock'  / The CAPS LOCK, NUM LOCK, and SCROLL LOCK keys
# 'insert'  /  The INS or INSERT key
# 'printscreen'  /  The PRTSC or PRINT SCREEN key
# 'winleft', 'winright'  /  The left and right WIN keys (on Windows)
# 'command'  /  The Command (image) key (on macOS)
# 'option'  /  The OPTION key (on macOS)
# 'volumemute', 'volumedown', 'volumeup' / The mute, volume down, and volume up keys (some
#                                           keyboards do not have these keys, but your
#                                           operating system will still be able to understand
#                                           these simulated keypresses)
#################################################################################


# 특수 문자 입력 - shift 4 -> $
pyautogui.keyDown('shift')
pyautogui.press('4')
pyautogui.keyUp('shift')

# 조합키(Hot Key) - Ctrl + A
pyautogui.keyDown('ctrl')
pyautogui.keyDown('a')
pyautogui.keyUp('a')  # press('a')
pyautogui.keyUp('ctrl')

# 간편한 조합키(Hot Key) - hotkey()
pyautogui.hotkey('ctrl', 'alt', 'shift' 'a')  # Ctrl + Alt + Shift + A
pyautogui.hotkey('ctrl', 'a') # Ctrl + A

# 한글 처리 - pip install pyperclip - 어떤 문장을 클립보드에 넣는 모듈
pyperclip.copy('나도코딩')
pyautogui.hotkey('ctrl', 'v')


def kor_write(text):
    pyperclip.copy(text)
    pyautogui.hotkey('ctrl', 'v')

kor_write('업무자동화 나도코딩')

 

■ 메시지 박스

import pyautogui

# countdown()
print('곧 시작합니다...')
pyautogui.countdown(3)  # 3 2 1
print('자동화 시작')

# alert(), confirm(), prompt(), password()
pyautogui.alert('확인 버튼만 있는 팝업 경고창..!', '경고')

result = pyautogui.confirm('계속 진행하겠습니까?', '확인')  # 확인, 취소 버튼
print(result)  # 확인 OK, 취소 Cancel 리턴

result = pyautogui.prompt('파일명을 무엇으로 하시겠습니까?', '입력')  # 사용자 입력
print(result)  # 입력값, 입력 안하면 None 출력

result = pyautogui.password('암호를 입력하세요.')
print(result)

 

■ 파이썬 자동화 로그 남기는 법

  • 레벨은 debug < info < warning < error < critical 순이며 설정 이상 레벨만 저장
  • debug 레벨 이상 모든 로그 저장하려면, DEBUG
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s')
# format='%(asctime)s [%(levelname)s] %(message)s' - 시간 [로그레벨] 사용자 메시지 형태 로그 작성

# 로그 레벨별 로그 남기기
logging.debug('이거 누가 짠거야 ~')  # 개발자 레벨 로그
logging.info('자동화 수행 준비')  # 사용자에게 보이는 정보
logging.warning('이 스크립트는 오래되었습니다. 실행상에 문제가 있을 수 있습니다')  # 경고 메시지
logging.error('에러가 발생했습니다. 에러 코드는 ...')
logging.critical('복구가 불가한 심각한 문제가 발생했습니다.')


## 터미널과 파일에 함께 로그 남기기
# 시간 [로그레벨] 메시지 형태 로그 작성
logFormatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger()  # 로거 가져오기
logger.setLevel(logging.DEBUG)  # 로그 레벨 설정

# 스트림 로그 출력 - 터미널(화면)
streamHandler = logging.StreamHandler()
streamHandler.setFormatter(logFormatter)
logger.addHandler(streamHandler)

# 파일
filename = datetime.now().strftime('mylogfile_%y-%m-%d_%H%M%S.log')  # 현재 시간으로 하되 형태로 변경
fileHandler = logging.FileHandler(filename, encoding='utf-8')
fileHandler.setFormatter(logFormatter)
logger.addHandler(fileHandler)

# Test
logger.debug('로그를 남겨보는 테스트를 진행합니다.')

 

댓글