직접 실험·검증한 가이드
작성 paper&honey 운영자 게시 수정
엑셀 CSV 한글 깨짐 — 바이트로 재현하고 BOM으로 끝내기
은행 거래내역이나 쇼핑몰 주문 목록을 CSV로 내려받아 엑셀에서 열면 머리글 "금액"이 "湲덉븸"으로, 이름 칸이 "源�誘쇱��"처럼 알아볼 수 없는 글자로 채워져 있습니다. 신기하게도 같은 파일을 메모장이나 구글 시트로 열면 멀쩡한데, 유독 엑셀에서만 반복됩니다. 이 글은 "인코딩이 안 맞아서 그렇다"로 끝내지 않습니다. 같은 한글을 세 가지 방식으로 저장해 바이트를 직접 비교하고, 엑셀이 저지르는 실수를 파이썬으로 그대로 재현해 그 깨진 글자가 정확히 어디서 나오는지까지 보여드립니다.
CSV엔 "나는 어떤 인코딩이다"라고 적을 자리가 없다
엑셀 기본 형식인 xlsx는 실은 여러 파일을 묶은 압축본이라, 그 안에 "이 문서는 어떤 방식으로 글자를 저장했다"는 정보를 함께 담습니다. 반면 CSV는 값을 쉼표로 나열한 순수 텍스트일 뿐입니다. 파일 어디에도 인코딩을 알려주는 자리가 없습니다. 그래서 CSV를 여는 프로그램은 매번 바이트만 보고 인코딩을 스스로 짐작해야 하고, 이 짐작이 틀리면 한글이 깨집니다. 문제의 뿌리는 여기, "짐작"에 있습니다.
같은 "김민준"인데 인코딩마다 바이트가 다르다
말로만 하면 와닿지 않으니 실제로 파일을 만들어 바이트를 들여다봤습니다. "이름,금액 / 김민준,1500" 두 줄짜리 CSV를 세 가지 방식으로 저장하고(파이썬 3.14.5), 맨 앞 16바이트를 16진수로 찍었습니다.
rows = "이름,금액\n김민준,1500\n"
open("utf8_nobom.csv","wb").write(rows.encode("utf-8"))
open("utf8_bom.csv","wb").write(b"\xef\xbb\xbf" + rows.encode("utf-8"))
open("cp949.csv","wb").write(rows.encode("cp949")) === 파일 맨 앞 16바이트 (hex) ===
utf8_nobom (29B): EC 9D B4 EB A6 84 2C EA B8 88 EC 95 A1 0A EA B9
utf8_bom (32B): EF BB BF EC 9D B4 EB A6 84 2C EA B8 88 EC 95 A1
cp949 (22B): C0 CC B8 A7 2C B1 DD BE D7 0A B1 E8 B9 CE C1 D8 세 파일 다 화면엔 똑같이 "이름,금액"으로 보이지만, 디스크에 담긴 바이트는 완전히 다릅니다. UTF-8은 "이"를 EC 9D B4 세 바이트로 적고, CP949는 같은 "이"를 C0 CC 두 바이트로 적습니다. 그래서 같은 두 줄인데 UTF-8은 29바이트, CP949는 22바이트로 크기부터 다릅니다. 이름 석 자 "김민준"만 떼어 비교하면 차이가 더 분명합니다.
utf-8 : EA B9 80 EB AF BC EC A4 80 (9바이트, 글자당 3)
cp949 : B1 E8 B9 CE C1 D8 (6바이트, 글자당 2) 여는 프로그램 입장에서 EA B9 80이라는 바이트를 만났을 때, 이걸 UTF-8 규칙으로 읽으면 "김"이 되고 CP949 규칙으로 읽으면 엉뚱한 글자가 됩니다. 같은 바이트, 다른 해석 — 깨짐은 전부 여기서 시작됩니다.
엑셀의 실수를 그대로 재현해봤다
엑셀이 .csv를 더블클릭으로 열 때, 파일에 명시적 표시가 없으면 한국어 윈도우에서는 시스템 로캘, 즉 전통적인 CP949로 읽으려는 동작을 오래 유지해 왔습니다. 요즘 웹 서비스가 내보내는 CSV는 대개 UTF-8이니, "UTF-8로 쓴 파일을 CP949로 읽는" 어긋남이 벌어집니다. 그 어긋남을 파이썬으로 똑같이 저질러 봤습니다. UTF-8 바이트를 일부러 CP949로 디코딩한 결과입니다.
utf8_bytes = "이름,금액\n김민준,1500\n".encode("utf-8")
print(utf8_bytes.decode("cp949", errors="replace"))
# 결과
이름,금액 → �씠由�,湲덉븸
김민준,1500 → 源�誘쇱��,1500 바로 이 "湲덉븸", "源쇱��" 같은 글자가 CSV 열었을 때 마주치는 그 깨짐입니다. 파일이 손상된 게 아니라, 멀쩡한 UTF-8 바이트를 엑셀이 옛 규칙으로 오독해서 만들어낸 허깨비입니다. 방향이 반대인 경우도 있습니다. 옛 시스템이 CP949로 내보낸 CSV를 요즘 편집기가 UTF-8로 읽으면 이렇게 됩니다.
cp949_bytes = "이름,금액\n김민준,1500\n".encode("cp949")
print(cp949_bytes.decode("utf-8", errors="replace"))
# 결과
이름,금액 → �̸�,�ݾ�
김민준,1500 → �����,1500 이쪽은 물음표 비슷한 대체문자(�)가 잔뜩 나옵니다. CP949 바이트 조합이 UTF-8 규칙에 맞지 않아 읽어낼 글자가 아예 없기 때문입니다. 정리하면, 깨진 모양만 봐도 방향을 짐작할 수 있습니다 — "湲덉븸"처럼 알 수 없는 한글이 나오면 UTF-8을 CP949로 읽은 것이고, "�̸�"처럼 대체문자가 쏟아지면 CP949를 UTF-8로 읽은 것입니다.
BOM 세 바이트가 이 짐작을 끝낸다
위 바이트 덤프에서 utf8_bom 파일만 맨 앞에 EF BB BF 세 바이트가 더 붙어 있는 걸 보셨을 겁니다. 이게 BOM(Byte Order Mark)입니다. 이 세 바이트는 실제 데이터가 아니라 순전히 "이 파일은 UTF-8입니다"라고 알리는 신호입니다. 근래 엑셀은 파일 앞에서 이 신호를 발견하면 로캘 짐작을 멈추고 UTF-8로 확정해 읽습니다. 그래서 BOM 붙은 CSV는 엑셀에서 더블클릭으로 열어도 한글이 멀쩡합니다. 반대로 BOM이 왜 가끔 문제를 일으키는지도 바이트로 확인됩니다. UTF-8 신호인 EF BB BF를 CP949로 오독하면 이렇게 나옵니다.
print(b"\xef\xbb\xbf".decode("cp949", errors="replace"))
# 결과: 癤
BOM을 이해하지 못하는 옛 프로그램에서 CSV 첫 칸 맨 앞에 "癤" 또는 ""가 붙어 보이는 게 바로 이 때문입니다. BOM은 엑셀에는 약이지만, BOM을 모르는 도구에는 이렇게 군더더기로 남습니다.
| 저장 방식 | 엑셀 더블클릭 | 구글 시트·메모장 | 파이썬(인코딩 명시) |
|---|---|---|---|
| UTF-8 (BOM 없음) | 깨짐 (湲덉븸) | 정상 | 정상 |
| UTF-8 + BOM | 정상 | 대개 정상(첫 칸에 BOM 잔여 가능) | 정상 |
| CP949 (EUC-KR) | 정상(윈도우) | 깨짐 (�̸�) | 인코딩 지정 필요 |
CP949는 어느 시절의 유산인가
왜 하필 CP949가 기본 짐작이 됐는지는 배경이 있습니다. 유니코드 이전, 한국어를 컴퓨터에 담으려고 만든 완성형 인코딩이 EUC-KR입니다. 자주 쓰는 한글 음절을 표로 묶어 글자당 두 바이트를 배정했습니다. 문제는 한글이 초·중·종성 조합으로 훨씬 많은 음절을 만드는데 EUC-KR 표엔 흔한 음절만 들어 있었다는 점입니다. 마이크로소프트가 빠진 음절까지 담아 확장한 것이 CP949이고, 한국어 윈도우가 오래전 기본으로 채택한 이래 지금도 전통 기본 인코딩 자리를 지키고 있습니다. 즉 CP949는 인터넷과 유니코드 이전, 한국어 윈도우 안에서만 통하던 약속이었습니다. 이 약속이 UTF-8이 표준이 된 지금의 웹 데이터와 부딪히는 지점이 바로 엑셀 CSV입니다.
그래서 뭘 하면 되나
CSV를 내가 직접 만드는 입장이라면, 내보낼 때부터 BOM 붙은 UTF-8로 저장하는 것이 가장 근본적인 예방입니다. 엑셀·구글 시트·메모장 어디서 열어도 깨질 여지를 없앱니다. 이미 BOM 없는 UTF-8이나 CP949로 만들어진 파일을 받은 입장이라면, 파일을 다시 만들어 달라고 요청하기보다 받은 쪽에서 인코딩을 바로잡는 편이 빠릅니다. paper&honey의 CSV 한글 깨짐 해결 도구는 인코딩을 자동 판별해 미리보기로 확인시켜 주고, 엑셀에서 바로 열리는 UTF-8(BOM 포함)로 다시 저장합니다. 파일은 서버로 올라가지 않고 브라우저 안에서만 처리됩니다.
이 실험이 다루지 못한 것
이 글의 바이트 덤프와 깨짐 재현은 전부 실제로 돌려 나온 값이지만, 정직하게 밝힐 것이 하나 있습니다 — 위 실험은 파이썬으로 엑셀의 동작을 "재현"한 것이지, 이 맥에서 실제 마이크로소프트 엑셀로 파일을 열어 확인한 것은 아닙니다. 엑셀이 로캘 기준으로 CSV를 읽고 BOM을 인식한다는 동작 자체는 널리 알려진 사실이고, CP949 오독이 만드는 깨짐 문자열은 위처럼 바이트 수준에서 확정적으로 재현됩니다. 다만 엑셀은 버전(특히 최신 Microsoft 365)에 따라 UTF-8 자동 감지가 개선된 경우가 있어, 환경에 따라 결과가 다를 수 있다는 점은 감안해 주세요.
헷갈리기 쉬운 것들
Q. 메모장에서는 멀쩡한데 왜 엑셀만 깨지나요? 요즘 텍스트 편집기는 인코딩 자동 판별이 UTF-8 우선으로 맞춰져 있는 반면, 엑셀은 .csv를 열 때 시스템 로캘(한국어 윈도우면 CP949)을 우선 기준으로 삼는 오래된 동작을 유지해 왔기 때문입니다. 같은 바이트를 서로 다른 규칙으로 읽는 것이라 유독 이 조합에서만 어긋남이 두드러집니다.
Q. UTF-8로 저장하기만 하면 항상 해결되나요? BOM 없는 UTF-8이면 웹·구글 시트에서는 문제없지만, 엑셀에서 더블클릭으로 열 땐 여전히 로캘로 짐작해 깨질 수 있습니다. 위 표대로 엑셀까지 안정적으로 열리게 하려면 UTF-8에 BOM(EF BB BF)까지 붙여 짐작 과정 자체를 건너뛰게 해야 합니다.
Q. 엑셀 안에서 인코딩을 골라 열 방법은 없나요? 데이터 탭의 "데이터 가져오기 및 변환"(버전에 따라 이름이 조금씩 다릅니다)을 쓰면 인코딩을 직접 지정해 가져올 수 있습니다. 다만 더블클릭으로 바로 여는 일상적 방식은 이 과정을 건너뛰고 로캘 짐작에 맡겨지므로, 매번 메뉴를 거치기보다 파일 자체를 BOM 포함 UTF-8로 바꿔두는 편이 번거로움이 적습니다.
결론
엑셀 CSV 한글 깨짐은 파일이 상한 게 아니라, 같은 바이트를 엑셀이 옛 규칙(CP949)으로 오독해 생기는 착시입니다. "湲덉븸"은 UTF-8을 CP949로, "�̸�"은 CP949를 UTF-8로 읽었다는 신호입니다. 해결의 핵심은 파일 앞에 BOM 세 바이트를 붙여 "나는 UTF-8"이라고 못 박아, 여는 쪽이 짐작할 여지를 없애는 것입니다. 받은 파일이 이미 깨졌다면 CSV 한글 깨짐 해결 도구로 브라우저에서 바로 UTF-8(BOM)로 다시 저장하면 됩니다. 이런 인코딩·표기 어긋남은 파일 이름에서도 똑같이 터집니다. 압축 파일 이름이 깨지는 문제는 zip 파일 한글 깨짐 가이드에서, 맥에서 보낸 파일이 윈도우에서 자소가 흩어지는 문제는 맥 자소분리 실측 가이드에서 같은 방식으로 바이트까지 파고들었습니다.