SameOS ~/tools/automation-lessons.md

Python 자동화 코드 강의: 중복 제거, 주기 실행, SQLite 기록

운영 자동화 코드는 그대로 공개하기보다 핵심 패턴만 떼어 설명할 때 더 오래 쓸 수 있습니다. 이 문서는 파일 정리, 반복 작업, 처리 이력 기록에서 자주 쓰는 구조를 작은 예제 세 개로 나눕니다. 모두 파이썬 코드이며, 파일로 저장한 뒤 터미널(명령어를 입력하는 검은 창)에서 python3 스크립트명.py 로 실행합니다.

1. 해시로 중복 파일 제거하기

파일명은 쉽게 바뀌지만 SHA-256 해시(파일 내용을 짧은 고유값으로 요약한 지문)는 파일 내용이 같을 때만 같습니다. 그래서 이름 대신 해시로 비교하면 중복을 정확히 찾을 수 있습니다. 대량 이미지 폴더에서는 썸네일 폴더를 건너뛰고, 원본 파일만 순회하는 식으로 범위를 먼저 제한하는 게 중요합니다.

  • 확장자 필터로 처리 대상 축소
  • 1MB 단위로 읽어 큰 파일도 메모리를 적게 사용
  • 같은 해시가 다시 나오면 중복 후보로 분류
from pathlib import Path
import hashlib

IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".gif"}

def file_hash(path: Path) -> str:
    # 파일을 1MB씩 나눠 읽어 SHA-256 해시(내용 지문)를 계산
    digest = hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()

def image_files(root: Path):
    for path in root.rglob("*"):
        if "thumbs" in path.parts:
            continue  # 썸네일 폴더는 건너뜀
        if path.is_file() and path.suffix.lower() in IMAGE_EXTS:
            yield path

def duplicate_groups(root: str):
    seen = {}
    for path in image_files(Path(root)):
        digest = file_hash(path)
        if digest in seen:
            yield seen[digest], path  # (기준 파일, 중복 후보)
        else:
            seen[digest] = path  # 처음 본 해시는 기준 파일로 기억

2. 작업이 겹치지 않는 주기 실행

자동화 작업은 예상보다 오래 걸릴 수 있습니다. 이전 작업이 끝나지 않았는데 다음 스캔이 시작되면 같은 파일을 두 번 처리하거나 파일 충돌이 생깁니다. "지금 실행 중"을 뜻하는 플래그(켜짐/꺼짐을 표시하는 변수)를 두고, 오류가 나도 반드시 실행되는 finally 블록에서 내려주는 구조가 안전합니다.

  • 실행 중이면 다음 회차는 건너뜀
  • 무거운 작업은 별도 스레드로 넘겨 이벤트 루프 정체 방지
  • 예외가 나도 상태 플래그를 복구
import asyncio

running = False

async def scan_once():
    # 느린 네트워크·디스크 작업은 여기에 둡니다.
    return await asyncio.to_thread(do_blocking_scan)

async def scheduler(interval_seconds: int):
    global running
    while True:
        if not running:  # 이전 작업이 아직 실행 중이면 이번 회차는 건너뜀
            running = True
            try:
                results = await scan_once()
                await send_results(results)
            finally:
                running = False  # 예외가 나도 플래그를 반드시 내림
        await asyncio.sleep(interval_seconds)

3. SQLite로 처리 이력 남기기

이미 처리한 항목을 다시 처리하지 않으려면 SQLite(별도 설치나 서버 없이 파일 하나로 쓰는 가벼운 데이터베이스 - 파이썬에 기본 내장) 테이블 하나면 충분합니다. PRIMARY KEY(같은 값이 두 번 저장되지 않는 고유 키)와 INSERT OR IGNORE를 쓰면 중복 기록을 자연스럽게 막을 수 있습니다.

  • 처리한 ID를 primary key로 저장
  • 재시작 후에도 이력이 유지됨
  • 설정값도 key-value 테이블로 함께 관리 가능
import sqlite3

DB_PATH = "worker.sqlite3"

def init_db():
    with sqlite3.connect(DB_PATH) as conn:
        conn.execute("""
            CREATE TABLE IF NOT EXISTS history (
                item_id TEXT PRIMARY KEY,
                saved_at INTEGER DEFAULT (unixepoch())
            )
        """)
        conn.execute("""
            CREATE TABLE IF NOT EXISTS settings (
                key TEXT PRIMARY KEY,
                value TEXT
            )
        """)

def seen(item_id: str) -> bool:
    # 이미 처리한 항목인지 확인
    with sqlite3.connect(DB_PATH) as conn:
        row = conn.execute(
            "SELECT 1 FROM history WHERE item_id = ?",
            (item_id,),
        ).fetchone()
    return row is not None

def remember(item_id: str):
    # 처리 완료를 기록 - 같은 ID는 중복 저장되지 않음
    with sqlite3.connect(DB_PATH) as conn:
        conn.execute(
            "INSERT OR IGNORE INTO history (item_id) VALUES (?)",
            (item_id,),
        )
공개할 때의 기준

실제 운영 코드에서 토큰, 쿠키, 계정명, 내부 URL, 비공개 폴더명은 제거해야 합니다. 공개 페이지에는 문제 해결 방식과 작은 예제만 남기면 사이트 콘텐츠로도 자연스럽고 보안에도 좋습니다.

SQLite 이력 예제 받기 (GitHub)