메뉴 닫기

파이썬 PyAutoGUI 장시간 자동화 최적화, 메모리 누수 모니터링과 주기적 재시작 전략

파이썬 PyAutoGUI 장시간 자동화 최적화, 메모리 누수 모니터링과 주기적 재시작 전략

🧰 클릭·키보드 매크로가 멈추기 전에, 메모리 감시와 안전 재시작으로 24시간 돌아가는 안정성을 확보하세요

끝도 없이 돌려야 하는 자동화가 어느 순간 흐트러지거나 멈춘 경험이 한 번쯤은 있을 것입니다.
장시간 반복되는 이미지 탐지와 이동, 스크린샷 처리 과정은 작게 쌓이는 누수만으로도 점차 속도를 떨어뜨리고 오류를 유발합니다.
현장에서 필요한 건 감과 요령이 아니라, 프로세스 메모리를 수치로 확인하고 임계값에서 스스로 회복하는 체계입니다.
이 글은 PyAutoGUI 기반 자동화를 안정적으로 유지하기 위한 핵심 원리와 실전 점검 포인트를 자연스럽게 연결해 드립니다.

핵심은 세 가지입니다.
첫째, 누수가 생기기 쉬운 지점을 미리 짚고 불필요한 객체와 이미지 버퍼 관리를 습관화하는 것.
둘째, psutil 같은 도구로 메모리 사용량과 핸들 수를 주기적으로 기록해 이상 징후를 조기에 감지하는 것.
셋째, 서비스가 잠깐 멈추더라도 전체 작업 흐름은 이어갈 수 있도록 안전 중단과 주기적 재시작 절차를 자동화하는 것입니다.
이 구조만 갖추면 야간 배치나 무인 운영에서도 신뢰도를 눈에 띄게 높일 수 있습니다.



🔗 PyAutoGUI 장시간 실행 구조와 메모리 누수의 흔한 원인

PyAutoGUI는 마우스 이동, 클릭, 키 입력, 스크린샷 등 OS 레벨 상호작용을 파이썬으로 제어하기에 적합합니다.
하지만 장시간 자동화에서는 작은 리소스 누적이 성능 저하나 비정상 종료로 이어질 수 있습니다.
특히 스크린샷을 반복 캡처하거나 이미지 매칭을 지속 수행하는 루프, 예외 발생 후 자원이 정리되지 않은 상태, 로깅과 캡처 파일이 끊임없이 쌓이는 상황에서 메모리 사용량과 핸들 수가 서서히 증가하는 패턴이 자주 관찰됩니다.
이 섹션은 왜 누수가 발생하는지, 구조적으로 어떤 지점을 점검해야 하는지를 정리해 장시간 안정성을 위한 토대를 마련합니다.

누수로 오해되는 현상도 있습니다.
파이썬 GC가 즉시 회수하지 않아 일시적으로 RSS가 커져 보이거나, 캡처 해상도가 높아 프레임당 이미지 버퍼가 커진 경우, 또는 고해상도/고주사율 디스플레이에서 DPI 스케일링 이슈로 스크린샷 비용이 증가하는 케이스입니다.
따라서 패턴(증가 추세)과 스파이크(일시 급증)를 구분해 관찰하고, 자원 반납과 주기적 초기화를 설계하는 것이 핵심입니다.

  • 🖼️반복 스크린샷으로 생성한 PIL Image 객체를 참조 해제하고 임시 파일을 남기지 않는지 점검
  • 🔍locateOnScreen 등 템플릿 이미지를 매 호출마다 디스크에서 로드하지 않도록 사전 로드/재사용
  • 🧹예외 발생 시 finally 블록에서 핸들/파일/버퍼 정리 및 상태 저장
  • 🧭고해상도 모니터에서 DPI 스케일링 설정 확인(좌표 오차·추가 리샘플링 비용 예방)
  • 🗂️로그/스크린샷 회전 정책 적용으로 디스크 폭증 방지
CODE BLOCK
import pyautogui as pag
from PIL import Image
import time, gc

# 반복 캡처시 임시 객체 정리 패턴 예시
for i in range(10000):
    img = pag.screenshot()          # PIL.Image
    # ... 이미지 매칭/분석 ...
    del img                          # 참조 해제
    if i % 200 == 0:
        gc.collect()                 # 주기적 수동 GC (옵션)
    time.sleep(0.05)

# 템플릿 이미지는 루프 밖에서 1회 로드해 재사용
template = Image.open("btn.png")
# ... locate / match 로직 ...
template.close()  # 명시적 close로 파일 핸들 정리

💬 메모리 누수는 ‘없애는 방법’만큼 ‘만들지 않는 습관’이 중요합니다.
이미지·파일·윈도우 핸들 등 외부 자원은 생성 위치와 해제 위치를 짝지어 관리하고, 루프 외부 초기화·루프 내부 최소 작업 원칙을 지키면 급격한 증가를 상당 부분 줄일 수 있습니다.

원인 설명 및 예방
반복 스크린샷 버퍼 누적 PIL Image 참조 해제 누락, 고해상도 캡처 빈도 과도.
루프 내 생성 최소화·주기적 gc.collect()·해상도/영역 캡처 사용.
템플릿 파일 재열기 locateOnScreen 호출마다 이미지 파일을 다시 여는 패턴.
전역 캐시/1회 로드 후 재사용, 처리 후 close로 핸들 해제.
예외로 인한 정리 누락 try/except만 두고 finally가 없어 버퍼·파일 미정리.
컨텍스트 매니저(with)·finally 블록으로 해제 보장.
DPI/멀티모니터 이슈 좌표 불일치로 재시도 증가, 불필요 캡처 추가.
DPI 일치, 단일 모니터 고정/가상데스크톱 활용.

💡 TIP: 장시간 자동화의 기본 전략은 작업 루프 최소화, 자원 수명 명시화, 주기적 초기화입니다.
PyAutoGUI 호출 사이 간격을 두고, 캡처 영역을 전체 화면 대신 ROI로 제한하면 메모리와 CPU 모두에서 이득을 봅니다.

🛠️ psutil로 프로세스 메모리 사용량 모니터링 구현

Python 환경에서 psutil은 CPU, 메모리, 디스크, 네트워크 등 시스템 자원을 손쉽게 조회할 수 있는 표준 라이브러리 역할을 합니다.
자동화 스크립트의 현재 메모리 사용량을 모니터링하려면 psutil.Process를 이용해 RSS(Resident Set Size) 또는 VMS(가상 메모리 크기)를 조회하는 방식이 일반적입니다.
다만 RSS 기준만으로는 공유 라이브러리 메모리까지 포함되기 때문에, 순수 할당 메모리 변화만 파악하려면 USS (Unique Set Size) 개념을 제공하는 툴을 병행하는 것이 더 정확할 수 있습니다.

아래는 psutil을 이용해 프로세스 메모리와 CPU 점유율을 주기적으로 기록하는 예시입니다.

CODE BLOCK
import psutil
import time
import os

def monitor(pid, interval=5):
    proc = psutil.Process(pid)
    while True:
        try:
            mem = proc.memory_info().rss / (1024 * 1024)  # MB 단위
            cpu = proc.cpu_percent(interval=0.1)         # 최근 0.1초 기준 퍼센트
            print(f"PID={pid} 메모리: {mem:.2f} MB, CPU: {cpu:.1f}%")
        except psutil.NoSuchProcess:
            print("프로세스가 종료됨")
            break
        time.sleep(interval)

if __name__ == "__main__":
    my_pid = os.getpid()
    monitor(my_pid)

이 스크립트를 자동화 스크립트와 함께 실행하거나, 별도 감시 프로세스로 돌리면 메모리 변화 흐름을 로그로 남길 수 있습니다.
CPU 측정 시 `cpu_percent()` 메소드의 첫 호출은 0.0을 반환할 수 있으니, 이를 보완하려면 두 번 호출하거나 `interval` 매개변수를 조절하는 방식이 권장됩니다.

이제 이 모니터링 데이터를 바탕으로, 어느 시점에서 누수가 시작되는지 감지할 수 있습니다. 기준선을 정하고 일정 수준 초과 시 자동 재시작이나 알림 트리거를 연결하면 안정성 제고가 가능합니다.



⚙️ 로그 회전과 경계값 알림으로 이상 감지 강화

장시간 자동화는 로그가 증거이자 보험입니다.
메모리 추세, 오류 스택, 재시작 사유를 남기면 원인 추적과 개선 속도가 빨라집니다.
하지만 무제한 로그는 디스크를 가득 채워 또 다른 장애를 만듭니다.
따라서 파일 크기 또는 시간 기준으로 자동 회전시키고, 일정 횟수만 보관하도록 정책을 정하는 것이 안전합니다.
여기에 임계치를 초과하는 순간을 실시간으로 알려주는 알림 채널을 더하면, 무인 운영에서도 대응이 가능해집니다.
핵심은 회전 정책알림 트리거를 코드로 고정해 누구나 동일하게 재현되는 운영 습관을 만드는 것입니다.

CODE BLOCK
import logging, logging.handlers, os, psutil, time, json, urllib.request

LOG_PATH = "runner.log"

# 1) 로그 회전: 파일 크기 기준 (5MB, 최대 7개 백업)
handler = logging.handlers.RotatingFileHandler(
    LOG_PATH, maxBytes=5 * 1024 * 1024, backupCount=7, encoding="utf-8"
)

# 2) 시간 기준 회전: 자정마다 새 파일(예: "runner.log.2025-10-06")
# handler = logging.handlers.TimedRotatingFileHandler(LOG_PATH, when="midnight", interval=1, backupCount=14, encoding="utf-8")

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s",
    handlers=[handler]
)

PID = os.getpid()
PROC = psutil.Process(PID)
MEM_LIMIT_MB = 800  # 임계값 예시
SPIKE_DELTA_MB = 150  # 급증 알림 기준
last_mem = None

def notify(text: str):
    # 간단한 웹훅 예시(슬랙/디스코드 호환 포맷 가정)
    try:
        data = json.dumps({"text": text}).encode("utf-8")
        req = urllib.request.Request("https://example.com/webhook", data=data, headers={"Content-Type":"application/json"})
        urllib.request.urlopen(req, timeout=3)
    except Exception as e:
        logging.warning(f"알림 전송 실패: {e}")

def sample_metrics():
    rss_mb = PROC.memory_info().rss / (1024 * 1024)
    cpu = PROC.cpu_percent(interval=0.1)
    handles = getattr(PROC, "num_handles", lambda: None)() if hasattr(PROC, "num_handles") else None
    return rss_mb, cpu, handles

if __name__ == "__main__":
    while True:
        rss_mb, cpu, handles = sample_metrics()
        logging.info(f"RSS={rss_mb:.1f}MB | CPU={cpu:.1f}% | HANDLES={handles}")
        # 임계값 초과 알림
        if rss_mb > MEM_LIMIT_MB:
            notify(f"[임계 초과] 메모리 {rss_mb:.1f}MB > {MEM_LIMIT_MB}MB")
        # 급증 감지 알림
        global last_mem
        if last_mem is not None and (rss_mb - last_mem) > SPIKE_DELTA_MB:
            notify(f"[급증] 메모리 +{rss_mb - last_mem:.1f}MB (현재 {rss_mb:.1f}MB)")
        last_mem = rss_mb
        time.sleep(10)

파일 크기 기준 회전은 폭주 로깅으로 단숨에 디스크가 찰 위험을 낮춰주고, 시간 기준 회전은 날짜 단위 분석과 보관 정책에 유리합니다.
운영 환경에서는 두 방식을 목적에 맞춰 택하거나, 모듈별로 혼합해도 좋습니다.
또한 알림은 과도하면 피로도를 유발하므로, 디바운스(일정 시간 내 1회)스레시홀드(임계 초과 지속 시간) 조건을 함께 적용해 신호 품질을 높이는 것이 좋습니다.

💬 로그는 나중을 위한 기록이 아니라, 지금의 결정을 돕는 데이터입니다.
회전 정책으로 가벼움을 유지하고, 알림 트리거로 대응 속도를 높이면 누수의 조짐을 “사건”이 되기 전에 차단할 수 있습니다.

정책 권장 값/예시
크기 기준 회전 maxBytes 5~20MB, backupCount 7~30.
고채널 환경은 5MB로 세분화.
시간 기준 회전 자정 또는 매시간.
분석·보관 단위를 날짜로 관리 시 유리.
알림 스레시홀드 RSS>800MB 또는 ΔRSS>150MB.
환경에 맞게 벤치마크 후 조정.
노이즈 제어 동일 이벤트 10분 내 1회, 지속 조건 30초 이상.

⚠️ 주의: 로그 파일을 네트워크 드라이브로만 저장하면 지연과 잠금 충돌이 발생해 자동화 속도에 영향을 줄 수 있습니다.
로컬에 우선 기록하고, 회전 시점에만 비동기 업로더로 외부 저장소에 동기화하는 방식을 권장합니다.

💡 TIP: 로그 라인에 세션 ID재시작 카운터를 포함하면, 재시작 전후의 흐름을 쉽게 이어서 분석할 수 있습니다.

🔌 Windows 작업 스케줄러와 systemd로 주기적 재시작

메모리 누수는 완벽히 막기 어렵습니다.
따라서 일정 주기로 스크립트를 재시작해 누적된 리소스를 초기화하는 전략이 실용적입니다.
운영체제에 내장된 스케줄링 도구를 활용하면 별도 모듈 없이도 쉽게 자동화 주기를 관리할 수 있습니다.
Windows 환경에서는 작업 스케줄러(Task Scheduler), Linux나 서버 환경에서는 systemd 서비스 타이머를 사용하면 안정적이고 간단하게 재시작이 가능합니다.

🪟 Windows 작업 스케줄러 설정 예시

1. 실행 파일 또는 스크립트(.py)를 지정합니다.
2. 트리거에서 “매일”, “매 4시간” 등 원하는 주기를 설정합니다.
3. 작업이 실행 중일 때 중복 실행되지 않도록 “이전 인스턴스 종료 후 재시작” 옵션을 선택합니다.
4. 시작 경로를 스크립트가 위치한 폴더로 지정해야 경로 문제 없이 작동합니다.

  • ⚙️프로그램/스크립트: python.exe
  • 📄인수 추가: “C:\path\to\auto_runner.py”
  • 🕒트리거: 4시간마다 반복, 무기한 실행
  • 🚫충돌 방지: “이전 인스턴스가 실행 중이면 종료 후 새 작업 시작”

🐧 Linux systemd 타이머 설정 예시

Linux 환경에서는 systemd 서비스와 타이머를 함께 이용해 일정 시간마다 자동으로 재시작할 수 있습니다.
아래는 예시 설정 파일입니다.

CODE BLOCK
[Unit]
Description=PyAutoGUI Runner

[Service]
ExecStart=/usr/bin/python3 /home/user/auto_runner.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

CODE BLOCK
[Unit]
Description=Restart PyAutoGUI Runner every 6 hours

[Timer]
OnBootSec=1min
OnUnitActiveSec=6h
Unit=auto_runner.service

[Install]
WantedBy=timers.target

이 두 파일을 `/etc/systemd/system/` 폴더에 저장하고, 아래 명령으로 등록하면 됩니다.

CODE BLOCK
sudo systemctl daemon-reload
sudo systemctl enable auto_runner.timer
sudo systemctl start auto_runner.timer

이 방식은 프로세스가 비정상 종료돼도 자동 복구가 이루어지고, 일정 주기마다 완전 재시작으로 메모리 누적을 제거할 수 있습니다.
Windows 작업 스케줄러와 달리 systemd는 로그를 journalctl로 통합 관리하므로, 별도 로깅 설정이 필요 없습니다.

💎 핵심 포인트:
“언제 재시작할지”보다 중요한 것은 “재시작을 안전하게 수행하는 방법”입니다.
스케줄러가 프로세스를 강제 종료하기 전에, PyAutoGUI 스크립트가 자신의 상태를 저장하도록 설계해야 데이터 유실을 막을 수 있습니다.



💡 안전한 중단과 상태 복구를 위한 체크포인트 설계

자동화 스크립트를 재시작할 때 가장 우려되는 문제는 ‘중단 시점 손실’입니다.
PyAutoGUI 기반 작업은 GUI 상태에 의존하기 때문에, 갑작스러운 종료가 발생하면 이전 마우스 위치나 진행 단계가 사라질 수 있습니다.
이를 방지하려면 주기적인 체크포인트(Checkpoint) 설계가 필수입니다.
즉, 스크립트의 현재 단계, 클릭 좌표, 마지막 실행 시간, 주요 변수 상태 등을 주기적으로 파일에 저장해 두었다가 재시작 시 이를 복원하는 구조를 갖추는 것입니다.

🧩 상태 저장을 위한 간단한 구현 예시

CODE BLOCK
import json, os, pyautogui as pag, time

STATE_FILE = "runner_state.json"

def save_state(step, coords):
    state = {"step": step, "coords": coords, "timestamp": time.time()}
    with open(STATE_FILE, "w", encoding="utf-8") as f:
        json.dump(state, f)

def load_state():
    if not os.path.exists(STATE_FILE):
        return {"step": 0, "coords": None}
    with open(STATE_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

# 예시 실행 흐름
state = load_state()
print(f"복원된 단계: {state['step']}")

for i in range(state["step"], 10):
    pag.click(100, 200)
    save_state(i, (100, 200))
    time.sleep(2)

이 방식으로 단계 정보를 JSON에 기록해두면, 재시작 시점에 중단 직전 상태로 이어서 수행할 수 있습니다.
로그와 체크포인트를 결합하면 재시작 후에도 손실 없이 흐름을 복원할 수 있으며, 전체 프로세스를 24시간 연속 실행하는 대신 “안전한 구간 단위”로 나누는 전략이 됩니다.

🧠 체크포인트 데이터 설계 시 주의할 점

  • 📦파일 저장 시 원자성(atomic write) 확보 — 임시 파일로 기록 후 rename
  • 🧮좌표, 단계 등은 절대값보단 논리적 단계명으로 관리 (UI 변동 대응)
  • 🕒저장 주기는 너무 잦지 않게 (성능 저하 방지) — 1분 단위 권장
  • 🔐민감 데이터(계정, 토큰)는 암호화 또는 제외 처리
  • 📁state 파일을 로그 디렉터리와 분리 — 회전/삭제의 영향을 받지 않도록

💬 재시작은 실패가 아니라 설계의 일부입니다.
안정적인 자동화는 ‘끊김 없는 동작’보다 ‘예측 가능한 복구’를 목표로 합니다.

💡 TIP: PyAutoGUI 스크립트를 서비스 형태로 장시간 운용할 경우, 체크포인트 파일 외에도 SQLiteTinyDB를 사용하면 상태 복원과 통계 집계가 더욱 견고해집니다.

자주 묻는 질문 (FAQ)

PyAutoGUI를 장시간 돌리면 왜 메모리가 계속 늘어나나요?
반복 스크린샷과 이미지 매칭 중 생성된 객체가 해제되지 않거나, 로그 파일이 누적되면서 메모리와 핸들이 서서히 증가합니다.
주기적인 gc.collect() 호출과 이미지 참조 해제, 로그 회전 정책을 병행하면 증가 추세를 완화할 수 있습니다.
psutil로 모니터링할 때 CPU 사용률이 0%로 표시되는 이유는?
psutil의 cpu_percent()는 첫 호출 시 비교 기준이 없기 때문에 항상 0.0을 반환합니다.
첫 호출 후 0.1초 이상 간격을 두고 다시 호출해야 실제 측정값이 표시됩니다.
Windows와 Linux 중 어떤 환경이 장시간 자동화에 더 안정적인가요?
GUI 제어가 주 목적이라면 Windows가 PyAutoGUI 호환성이 더 높습니다.
다만 장시간 서비스 운영 측면에서는 Linux + systemd 환경이 복구 및 자원 관리가 안정적입니다.
메모리 임계값은 어느 정도로 설정하는 게 적절한가요?
일반적으로 Python 프로세스의 RSS가 700~900MB를 초과하면 점진적인 성능 저하가 발생합니다.
주기적 재시작 기준은 800MB 내외로 설정하는 것이 안전합니다.
주기적 재시작 시 데이터 유실 없이 이어서 실행할 수 있나요?
가능합니다.
체크포인트(현재 단계, 위치 등)를 JSON이나 SQLite로 기록하고 재시작 시 복원하는 구조를 갖추면 중단 지점부터 자동으로 이어집니다.
자동화 도중 모니터를 끄면 PyAutoGUI가 멈추는 이유는?
일부 그래픽 드라이버는 모니터 절전 시 FrameBuffer를 해제합니다.
이때 스크린샷 함수가 빈 이미지를 반환하며 오류가 발생할 수 있습니다.
모니터 절전 기능을 비활성화하거나 가상 디스플레이를 사용하는 것이 좋습니다.
PyAutoGUI 외에 더 안정적인 대안이 있나요?
단순 GUI 자동화 외에도 pywinauto, autopy, SikuliX 등의 프레임워크가 있습니다.
UI 접근 방식과 메모리 관리 정책이 달라, 장시간 안정성 테스트를 통해 선택하는 것이 바람직합니다.
자동화 중 에러 발생 시 자동으로 재시작할 수 있나요?
네.
try/except 블록에서 예외 발생 시 상태를 저장하고, os.execv()를 호출해 프로세스를 재시작하는 구조를 만들 수 있습니다.
단, 무한 재시작 루프를 방지하기 위해 재시도 횟수를 제한하거나 대기 시간을 두는 것이 좋습니다.

🧭 PyAutoGUI 장시간 자동화 운영 요약과 실전 체크리스트

PyAutoGUI 기반 장시간 자동화의 핵심은 누수를 만들지 않는 구조, 이상 징후를 조기에 발견하는 모니터링, 안전하게 되살리는 재시작입니다.
이미지 캡처와 템플릿 재사용, 예외 이후 자원 정리, ROI 기반 스크린샷처럼 루프 내부 부담을 줄이면 증가 추세를 크게 완화할 수 있습니다.
psutil로 RSS와 CPU, 핸들 수를 기록하고 회전 로그 정책으로 디스크 폭증을 막으면 분석과 보관이 용이합니다.
임계 초과·급증 알림을 트리거로 재시작을 연결하되, 상태 파일로 이어달리기 구조를 갖추면 무인 운영 신뢰도가 높아집니다.
Windows 작업 스케줄러 또는 Linux systemd 타이머로 주기적 초기화와 장애 복구를 표준화하면 24시간 운영 품질을 꾸준히 유지할 수 있습니다.


🏷️ 관련 태그 : PyAutoGUI, 파이썬자동화, 메모리누수, psutil, 로그회전, 작업스케줄러, systemd, 체크포인트, 무중단운영, 재시작전략