ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] 파이썬을 활용한 업무자동화 - 데스크탑 자동화(pyautogui - 파이썬으로 마우스, 키보드 제어하기) 연습 코드 정리
    코딩 연습/코딩배우기 2021. 7. 6. 20:57
    반응형

    수년 전 '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('로그를 남겨보는 테스트를 진행합니다.')

     

    반응형
Designed by goodthings4me.