CSV 문제 해결 가이드
pandas CSV 오류 해결: UnicodeDecodeError, ParserError, EmptyDataError
pandas read_csv에서 발생하는 UnicodeDecodeError, UnicodeEncodeError, ParserError, EmptyDataError를 인코딩·구조·입력 문제로 나눠 원본 손실 없이 진단하는 방법을 설명합니다.
pandas.read_csv()가 실패했을 때 인터넷에서 찾은 encoding= 값을 하나씩 넣거나 on_bad_lines="skip"을 추가하면 예외 메시지는 사라질 수 있습니다. 하지만 오류가 사라진 것과 데이터를 정확히 읽은 것은 다릅니다. 잘못된 글자가 정상 문자처럼 보이거나 문제 행 전체가 빠질 수 있기 때문입니다.
먼저 예외를 어느 단계의 실패인지 나누면 수정 범위가 작아집니다.
| 오류 | 실패한 단계 | 오류만으로 알 수 없는 것 |
|---|---|---|
UnicodeDecodeError |
바이트를 지정한 문자 인코딩으로 해석 | 실제 작성자가 의도한 인코딩 |
UnicodeEncodeError |
문자열을 대상 인코딩의 바이트로 저장 | 어떤 출력 형식이 업무 시스템에 맞는지 |
pandas.errors.ParserError |
해석된 텍스트를 행·열로 파싱 | 구분자·따옴표·손상 행 중 정확한 원인 |
pandas.errors.EmptyDataError |
읽을 데이터나 헤더를 찾지 못함 | 파일 선택·파일 내용·스트림 위치 중 무엇이 문제인지 |
공통 진단 순서
- 원본 파일을 복사하고 복사본으로 작업합니다.
- 실제로 연 파일의 경로와 바이트 크기를 출력합니다.
- 원시 바이트를 엄격하게 디코딩해 첫 실패 위치와 후보 미리보기를 비교합니다.
- 인코딩이 확정된 뒤 구분자·따옴표·행별 열 수를 검사합니다.
read_csv()옵션을 명시하고 오류 행을 건너뛰지 않은 상태로 다시 읽습니다.- 행 수, 열 수, 대표 한글 값, 앞자리 0과 긴 식별번호를 원본과 대조합니다.
인코딩 오류와 CSV 구조 오류를 동시에 바꾸지 않는 것이 핵심입니다. 두 설정을 한꺼번에 바꾸면 무엇이 실제 원인이었는지 알 수 없습니다.
UnicodeDecodeError: 읽는 인코딩이 바이트와 맞지 않습니다
대표 메시지는 다음과 같습니다.
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x.. in position ...
이 오류는 해당 위치의 바이트열이 지정한 디코더의 규칙에 맞지 않는다는 뜻입니다. 곧바로 “이 파일은 CP949다”라는 뜻은 아닙니다. 파일이 다른 인코딩일 수도 있고, 일부 바이트만 손상됐거나 서로 다른 인코딩의 텍스트가 섞였을 수도 있습니다.
작은 진단 코드로 후보별 엄격 디코딩 결과와 실패 바이트 위치를 분리해 볼 수 있습니다.
from pathlib import Path
path = Path("orders.csv")
raw = path.read_bytes()
print(path.resolve(), len(raw))
for encoding in ("utf-8-sig", "cp949"):
try:
text = raw.decode(encoding, errors="strict")
except UnicodeDecodeError as error:
print(encoding, "실패 바이트:", error.start, "이유:", error.reason)
else:
print(encoding, "성공 미리보기:", repr(text[:120]))
두 후보가 모두 성공해도 결과가 같다는 보장은 없습니다. 파일을 만든 시스템의 내보내기 설정, 예상되는 한글 값, BOM과 대표 행을 함께 확인해야 합니다. UTF-8 BOM이 있는 파일은 Python의 utf-8-sig 코덱으로 읽으면 선두 표시를 제거할 수 있습니다.
Python Unicode HOWTO는 UTF-8 BOM과 utf-8-sig의 관계를 설명하고, Python codecs 공식 문서는 기본 strict가 오류를 발생시키는 반면 ignore는 잘못된 데이터를 알림 없이 버리고 replace는 디코딩 때 �로 대체한다고 설명합니다. 따라서 원문 손실이 허용되지 않는 업무 파일에서 encoding_errors="ignore"를 해결책으로 사용하면 안 됩니다.
인코딩 후보를 판정하는 더 자세한 기준은 CSV 한글 깨짐 가이드에서 확인할 수 있습니다.
UnicodeEncodeError: 읽기가 아니라 저장 단계일 수 있습니다
대표적으로 Python 문자열을 CP949로 내보낼 때 대상 문자셋에 없는 문자가 포함되면 발생합니다.
UnicodeEncodeError: 'cp949' codec can't encode character ...
실패 문자를 먼저 찾은 뒤, 받는 시스템이 UTF-8을 지원하는지 확인합니다.
try:
output = text.encode("cp949", errors="strict")
except UnicodeEncodeError as error:
print("표현할 수 없는 문자열 범위:", error.start, error.end)
print("문자:", repr(error.object[error.start:error.end]))
errors="replace"는 표현할 수 없는 문자를 ?로 바꿀 수 있으므로 가역 변환이 아닙니다. 출력 시스템이 UTF-8을 받을 수 있다면 UTF-8을 선택하고, CP949가 필수라면 치환 대상과 승인 규칙을 별도로 기록해야 합니다. 오류를 무시해서 파일이 생성됐다는 사실만으로 데이터 보존을 확인할 수는 없습니다.
ParserError: 디코딩 후의 표 구조를 확인하세요
pandas의 ParserError 공식 설명은 read_csv 같은 함수가 파일 내용을 파싱하다 만난 일반 오류라고 정의합니다. 다음과 비슷한 메시지가 흔합니다.
ParserError: Error tokenizing data. C error: Expected 3 fields in line 8, saw 5
가능한 원인은 하나가 아닙니다.
- 실제 파일은 세미콜론이나 탭 구분인데
sep=","로 읽음 - 값 안의 쉼표가 큰따옴표로 감싸지지 않음
- 큰따옴표가 열렸지만 레코드 끝까지 닫히지 않음
- 인용 필드 안의 큰따옴표가
""로 이스케이프되지 않음 - 특정 행만 헤더와 다른 수의 필드를 가짐
오류 메시지의 줄 번호만 텍스트 편집기의 같은 줄과 바로 대응시키는 것도 주의해야 합니다. 인용된 필드 안에 줄바꿈이 있으면 하나의 논리 레코드가 여러 물리 줄을 차지할 수 있습니다. 문제 행의 앞뒤 레코드와 따옴표 시작 위치를 함께 봐야 합니다.
설정이 확인되면 다음처럼 명시합니다.
import pandas as pd
df = pd.read_csv(
"orders.csv",
encoding="cp949",
encoding_errors="strict",
sep=",",
quotechar='"',
dtype=str,
keep_default_na=False,
on_bad_lines="error",
)
pandas read_csv 공식 문서는 sep, quotechar, encoding, encoding_errors를 각각 지정할 수 있고, on_bad_lines="warn" 또는 "skip"은 필드가 너무 많은 문제 행을 건너뛴다고 명시합니다. 누락이 허용되지 않는 데이터에서 skip은 수리가 아니라 폐기이므로, 먼저 error 상태에서 원인을 찾으세요.
열이 전부 한 칸으로 들어가는 경우에는 CSV 구분자 진단 가이드에서 쉼표·세미콜론·탭을 구분하는 절차를 확인할 수 있습니다.
EmptyDataError: 읽은 대상이 정말 비어 있는지 확인하세요
pandas.errors.EmptyDataError: No columns to parse from file
pandas의 EmptyDataError 공식 설명은 read_csv가 빈 데이터나 헤더를 만났을 때 발생하는 예외라고 설명합니다. 다음을 순서대로 확인합니다.
from pathlib import Path
path = Path("orders.csv")
print("경로:", path.resolve())
print("존재:", path.exists())
print("바이트:", path.stat().st_size if path.exists() else None)
print("선두 바이트:", path.read_bytes()[:80] if path.exists() else None)
- 파일 크기가 0인지
- 공백이나 줄바꿈만 들어 있는지
- 예상한 파일이 아니라 같은 이름의 빈 파일을 열었는지
- 파일 객체를 앞에서 이미 읽어 포인터가 끝에 있는지
comment=나skiprows=설정이 모든 유효 행을 제외했는지
파일 객체를 재사용했다면 seek(0)으로 시작 위치를 확인한 뒤 다시 읽습니다. 단, seek(0)은 진짜 빈 파일이나 잘못 선택한 경로를 고치는 방법이 아닙니다. 압축 파일·XLSX·서버가 반환한 HTML을 .csv라는 이름만 보고 넘긴 경우도 있으므로 선두 바이트와 실제 형식을 확인하세요.
CSV 파일 진단 도구와 pandas의 역할은 다릅니다
CSV 파일 진단 도구는 pandas를 실행하거나 코드를 자동 수정하지 않습니다. 대신 pandas에 넣기 전 원시 파일에서 다음 근거를 확인합니다.
- UTF-8·CP949 계열 후보와 엄격 디코딩 실패 바이트
- UTF-8 BOM 또는 지원하지 않는 UTF-16/32 BOM
- 쉼표·탭·세미콜론·파이프 후보와 열 수 일관성
- 닫히지 않은 따옴표와 헤더 대비 열 수 불일치 위치
- 앞자리 0, 긴 정수, 날짜 유사 값 같은 Excel 위험
CSV 파일 진단 도구는 최대 5MiB만 분석하며 UTF-16/32 본문, 압축 파일, XLSX와 모든 pandas 파서 옵션을 지원하지 않습니다. 또한 디코딩에 성공했다는 사실만으로 작성자의 의도를 확정하지 않습니다. 도구가 제시한 후보와 파일 생성 시스템의 명세를 대조한 뒤 그 값을 read_csv()에 명시하세요.
예외가 사라진 뒤 반드시 대조할 것
- 원본의 예상 행 수와
len(df) - 예상 열 이름과
df.columns - 한글 이름·주소의 대표 값
- 앞자리 0이 있는 전화번호·우편번호
- 16자리 이상 주문번호의 마지막 자리
- 쉼표와 줄바꿈이 들어간 메모 필드
- 건너뛴 행이 없는지 확인한 로그
가장 안전한 해결은 “읽히는 옵션”을 찾는 것이 아니라 왜 그 옵션이 맞는지 근거를 남기고, 읽은 결과가 원본 계약과 일치하는지 검증하는 것입니다.