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, 비공개 폴더명은 제거해야 합니다. 공개 페이지에는 문제 해결 방식과 작은 예제만 남기면 사이트 콘텐츠로도 자연스럽고 보안에도 좋습니다.