[파이썬 100강] 51강. pathlib와 glob으로 대량 파일 일괄 처리 자동화하기
파일 자동화 작업을 처음 만들 때 가장 흔한 오해는 “경로는 그냥 문자열 붙이면 된다”입니다. 로컬에서 잠깐 돌리는 스크립트라면 그럭저럭 동작할 수 있지만, 운영 환경이 바뀌거나 OS가 달라지는 순간 경로 구분자(/, \\) 문제, 숨김 파일 누락, 확장자 대소문자 이슈 같은 작은 차이들이 연달아 터집니다. 이번 강의의 목표는 단순히 Path("...")를 쓰는 법을 아는 수준이 아니라, 많은 파일을 안전하게 찾고, 필터링하고, 가공하고, 결과를 재현 가능하게 남기는 습관을 만드는 것입니다. 특히 초보 단계에서는 “파일이 있을 것”을 전제로 코드를 쓰기 쉬운데, 실제 자동화는 “없을 수도 있고, 권한이 없을 수도 있고, 이름이 예상과 다를 수도 있다”를 기본값으로 두고 시작해야 합니다.
핵심 개념
pathlib.Path는 경로를 문자열이 아닌 객체로 다루게 해 주며, 경로 결합/검사/순회 코드를 훨씬 안전하고 읽기 쉽게 만듭니다.glob패턴(*.csv,**/*.log)은 “어떤 파일을 처리할지”를 선언적으로 표현하므로, 반복문 안 조건문을 줄이고 실수를 예방합니다.- 대량 파일 자동화의 본질은 “찾기(find) → 검증(validate) → 처리(process) → 기록(log)”의 단계 분리입니다.
pathlib를 배우면 코드가 단순히 예뻐지는 것이 아니라, 유지보수 비용이 내려갑니다. 예를 들어 문자열 덧셈으로 경로를 만들면 "/data" + "report.csv"처럼 슬래시 누락 실수가 생기기 쉽습니다. 반면 Path("/data") / "report.csv"는 경로 조합 의도가 명확하고, 운영체제마다 다른 표현을 내부에서 알아서 처리합니다. 또한 glob는 파일 탐색 규칙을 코드에 드러내 줍니다. “하위 폴더 포함 CSV만 찾는다”라는 요구를 rglob("*.csv") 한 줄로 표현할 수 있으니, 팀원이 읽었을 때도 무엇을 의도했는지 바로 이해할 수 있습니다.
그리고 자동화에서 가장 중요한 포인트는 “모든 파일을 똑같이 신뢰하지 않는다”입니다. 파일은 이름이 같아도 인코딩이 다를 수 있고, 크기가 0바이트일 수도 있으며, 권한 문제로 읽기 실패가 날 수 있습니다. 그래서 이번 강의에서는 파일 찾기보다도, 찾은 뒤 무엇을 확인하고 어디까지 처리할지를 함께 다룹니다.
왜 중요한가
실무에서 파일 일괄 처리 자동화는 생각보다 자주 등장합니다. 로그 정리, 리포트 병합, 이미지 리사이즈, 백업 검증, 데이터 전처리 등 거의 모든 팀이 한 번은 겪는 문제입니다. 그런데 이 작업을 급하게 문자열 기반으로 작성하면, 처음에는 빨라 보여도 곧 유지보수 지옥이 열립니다. 파일 수가 늘어날수록 예외 케이스가 폭증하고, “왜 어떤 파일은 누락됐지?” 같은 질문에 즉시 답하기 어려워집니다.
pathlib + glob 패턴을 초기에 잡아 두면 좋은 이유는 명확합니다. 첫째, 경로 처리의 휴먼 에러를 줄입니다. 둘째, 처리 대상 선정 규칙이 코드에 명시됩니다. 셋째, 로깅 지점(몇 개 발견/몇 개 성공/몇 개 실패)을 구조적으로 넣기 쉬워집니다. 넷째, 나중에 병렬 처리나 스케줄링으로 확장할 때도 단계 분리가 이미 되어 있어 리팩터링 비용이 낮습니다.
쉽게 말해, 이 주제는 “편의 문법”이 아니라 자동화 신뢰도의 시작점입니다. 스크립트가 한 번 돌아가는 것과 매일 안심하고 돌릴 수 있는 것은 완전히 다른 문제고, 그 차이를 만드는 기초가 바로 이 강의 내용입니다.
기본 사용
예제 1) 가장 기본 패턴: 안전한 경로 조합과 존재 확인
>>> from pathlib import Path
>>> base = Path("data")
>>> target = base / "2026" / "sales.csv"
>>> str(target)
'data/2026/sales.csv'
>>> target.exists()
False
해설:
Path객체끼리/연산자로 경로를 결합하면, 슬래시를 직접 신경 쓰지 않아도 됩니다.- 자동화 전에
exists(),is_file(),is_dir()같은 기본 검사를 습관화하면 실패를 앞단에서 차단할 수 있습니다.
예제 2) 조건/반복/조합 확장: 특정 패턴 파일만 수집
>>> from pathlib import Path
>>> root = Path("logs")
>>> files = sorted(root.glob("*.log"))
>>> [f.name for f in files][:3]
['app.log', 'batch.log', 'worker.log']
>>> len(files) >= 0
True
해설:
glob("*.log")는 현재 디렉터리 기준 패턴 매칭입니다.sorted()로 정렬해 두면 실행할 때마다 처리 순서가 같아져 재현성과 디버깅 편의가 좋아집니다.
예제 3) 실전형 미니 케이스: 하위 폴더 재귀 탐색 + 크기 필터
>>> from pathlib import Path
>>> root = Path("archive")
>>> candidates = [p for p in root.rglob("*.csv") if p.stat().st_size > 0]
>>> len(candidates) >= 0
True
>>> all(p.suffix == ".csv" for p in candidates)
True
해설:
rglob는 하위 폴더까지 재귀 탐색하므로 대량 파일 작업에 자주 쓰입니다.- 0바이트 파일 제외 같은 기본 검증을 수집 단계에서 넣으면, 뒤 단계의 파싱 오류를 크게 줄일 수 있습니다.
예제 4) 처리 결과를 구조적으로 남기기
>>> from pathlib import Path
>>> processed, failed = [], []
>>> for p in Path("input").glob("*.txt"):
... try:
... text = p.read_text(encoding="utf-8")
... processed.append((p.name, len(text)))
... except Exception as e:
... failed.append((p.name, type(e).__name__))
>>> isinstance(processed, list) and isinstance(failed, list)
True
해설:
- “성공 목록”과 “실패 목록”을 분리하면 작업이 끝난 뒤 보고서 형태로 바로 출력할 수 있습니다.
- 모든 예외를 무시하지 말고, 최소한 파일명과 예외 타입은 기록해야 재처리 기준을 세울 수 있습니다.
자주 하는 실수
실수 1) 문자열로 경로를 이어 붙이다가 구분자/중복 슬래시 오류 내기
>>> base = "data/2026"
>>> file_name = "report.csv"
>>> bad = base + file_name
>>> bad
'data/2026report.csv'
원인:
- 경로를 텍스트로만 취급하면 슬래시 유무를 매번 사람이 기억해야 해서 실수가 반복됩니다.
해결:
>>> from pathlib import Path
>>> good = Path("data") / "2026" / "report.csv"
>>> str(good)
'data/2026/report.csv'
Path 객체를 쓰면 경로 결합 규칙이 코드 자체에 반영됩니다. 팀원 리뷰에서도 “왜 이렇게 만들었는지”가 눈에 보여 실수 확률이 줄어듭니다.
실수 2) glob 결과가 generator라는 점을 잊고 여러 번 소비하기
- 증상: 첫 번째 반복에서는 파일이 보이는데, 두 번째 반복에서는 비어 있는 것처럼 보입니다.
- 원인:
glob/rglob결과를 바로 순회하면서 한 번 소비한 뒤 재사용하려고 했습니다. - 해결: 여러 단계에서 써야 한다면
list()로 즉시 materialize 해 두고, 정렬까지 같이 처리합니다.
>>> from pathlib import Path
>>> paths = list(Path("data").rglob("*.json"))
>>> len(paths) >= 0
True
>>> paths = sorted(paths)
>>> paths[:2] if paths else []
[]
실수 3) 파일명 확장자만 믿고 내용을 검증하지 않음
- 증상:
.csv파일로 처리했는데 실제로는 깨진 인코딩/빈 파일이라 파싱이 실패합니다. - 원인: “이름이 맞으면 내용도 맞다”라고 가정했습니다.
- 해결: 처리 전 최소 검증(파일 크기, 샘플 읽기, 예상 컬럼 유무)을 넣고 실패 파일을 별도 큐로 보냅니다.
실무에서는 “정상 파일만 온다”는 전제가 거의 성립하지 않습니다. 그래서 파일 탐색 코드는 단순해도 되지만, 검증 코드는 절대 생략하면 안 됩니다.
실무 패턴
- 입력 검증 규칙: 수집 직후
exists/is_file/st_size를 체크하고, 인코딩/헤더 검증을 통과한 파일만 본 처리 단계로 보냅니다. - 로그/예외 처리 규칙: 파일 단위
try/except를 사용해 전체 배치가 멈추지 않게 하되, 실패 건은 반드시 파일명·예외타입·시간을 남깁니다. - 재사용 구조화 팁:
discover_files(),validate_file(),process_file(),summarize_result()처럼 함수를 쪼개면 테스트와 재실행이 쉬워집니다. - 성능/메모리 체크 포인트: 파일 목록이 매우 클 때는 한 번에 전부 읽지 말고 iterator 기반 처리로 바꾸고, 필요하면 처리 단위를 배치(batch)로 묶습니다.
추가로, 운영 자동화에서는 “실패를 다시 실행할 수 있는가”가 핵심입니다. 그래서 성공/실패 목록을 JSON이나 CSV로 남겨 두면 다음 실행에서 실패 파일만 재시도하는 전략을 쉽게 구현할 수 있습니다. 이 한 가지 습관만으로도 야간 배치 운영 난이도가 크게 내려갑니다.
오늘의 결론
한 줄 요약: 파일 자동화의 품질은 경로 문자열 요령이 아니라, pathlib 기반 구조화와 검증 습관에서 결정됩니다.
기억할 것:
Path로 경로를 객체화하면 코드가 안전해지고 읽기 쉬워집니다.glob/rglob는 탐색 규칙을 선언적으로 표현해 실수를 줄입니다.- 대량 처리에서는 “발견 수/성공 수/실패 수 + 실패 사유”를 반드시 남겨야 운영이 됩니다.
연습문제
reports/하위 폴더 전체에서.csv파일만 찾고, 0바이트 파일은 제외한 리스트를 반환하는discover_csv_files(root)함수를 작성하세요.- 발견한 파일 목록을 이름 기준 오름차순으로 정렬한 뒤, 파일명과 크기(byte)를 튜플로 묶어 출력하세요.
- 각 파일을 UTF-8로 읽어 첫 줄만 수집하는 함수를 작성하고, 읽기 실패한 파일은
(파일명, 예외타입)형태로failed리스트에 저장하세요.
이전 강의 정답
- 문자열 설정값 안전 변환 + 1~100 범위 보정
>>> def parse_level(raw, default=10):
... try:
... value = int(raw)
... except (TypeError, ValueError):
... return default
... return max(1, min(value, 100))
>>> parse_level("10"), parse_level("-3"), parse_level("abc")
(10, 1, 10)
active=True만 합산 + 필드 누락 기본값 처리
>>> rows = [
... {"active": True, "score": 7},
... {"active": False, "score": 100},
... {"active": True},
... ]
>>> total = sum(r.get("score", 0) for r in rows if r.get("active") is True)
>>> total
7
- 실패 입력 요약 보고(총 건수, 사유별 개수)
>>> failed = [
... {"id": 1, "reason": "missing_field"},
... {"id": 2, "reason": "type_error"},
... {"id": 3, "reason": "missing_field"},
... ]
>>> from collections import Counter
>>> summary = Counter(item["reason"] for item in failed)
>>> len(failed), dict(summary)
(3, {'missing_field': 2, 'type_error': 1})
실습 환경/재현 정보
- 실행 환경:
condaenvpython100(Python 3.11.14) - 가정한 OS: macOS/Linux 공통
- 권장 실행:
python -qREPL에서 예제 검증 후, 스크립트화할 때는Path기반 함수로 분리 - 검증 포인트: 샘플 디렉터리에서 발견 수/성공 수/실패 수를 출력해 재현성 확인