pandas to_csv로 저장한 CSV를 엑셀에서 열면 한글이 깨질 때: encoding='utf-8-sig' 해결법
df.to_csv("파일.csv", index=False)로 저장한 CSV를 엑셀에서 열었을 때 한글이 알 수 없는 글자로 보이는 것은 pandas가 파일을 BOM(파일 맨 앞의 UTF-8 표시)이 없는 UTF-8로 저장하기 때문입니다. 엑셀이 표시가 없는 파일을 UTF-8로 알아보지 못하는 경우에 생기는 현상입니다. 저장할 때 df.to_csv("파일.csv", index=False, encoding="utf-8-sig")로 바꾸면 파일 앞에 BOM 3바이트(ef bb bf)가 붙고, Microsoft는 BOM이 있는 UTF-8 CSV는 엑셀에서 정상으로 열린다고 안내합니다. 아래의 파일 바이트와 pandas 다시 읽기는 직접 실행한 결과이고, 엑셀 쪽 동작은 Microsoft 지원 문서를 따릅니다.
확인한 환경
OS: Windows-11-10.0.26200-SP0
Python: 3.14.7
pandas: 3.1.0
이 글에서 직접 실행한 것은 다음 세 가지입니다.
- 저장된 파일의 첫 바이트 확인
- pandas로 다시 읽기
- 파일 바이트를 한국어 윈도우의 기본 글자표(cp949)로 풀어 본 문자열 확인
마지막 항목은 파이썬이 해석한 결과이고, 엑셀이 같은 글자표로 읽을 때 어떤 모양이 되는지 가늠하는 용도입니다.
해결 방법
저장 코드의 to_csv에 encoding="utf-8-sig"를 추가합니다.
import pandas as pd
df = pd.DataFrame({"이름": ["김민준", "이서연"], "품목": ["사과", "배"], "금액": [12000, 8500]})
df.to_csv("주문.csv", index=False, encoding="utf-8-sig")
utf-8-sig는 UTF-8로 쓰되 맨 앞에 BOM을 붙이는 파이썬의 글자표 이름입니다(Python 공식 문서). 아래에서 이 한 줄이 파일에 실제로 무엇을 바꾸는지 확인합니다.
파일 첫 바이트 비교
같은 데이터를 기본값과 utf-8-sig로 저장해 첫 16바이트를 찍었습니다.
기본 to_csv 첫 16바이트 : ec 9d b4 eb a6 84 2c ed 92 88 eb aa a9 2c ea b8
utf-8-sig 첫 16바이트 : ef bb bf ec 9d b4 eb a6 84 2c ed 92 88 eb aa a9
기본 파일 크기: 66 utf-8-sig 파일 크기: 69
BOM(ef bb bf)으로 시작? 기본: False utf-8-sig: True
한글 이름은 두 파일 모두 같은 바이트(ec 9d b4 eb a6 84)로 들어 있고, utf-8-sig 쪽에만 맨 앞에 ef bb bf가 더 있습니다. 파일 크기 차이 3바이트가 그것입니다. 즉 내용은 똑같고, "이 파일은 UTF-8입니다"라는 표시만 추가됩니다. pandas 기본값(encoding=None)이 UTF-8이라는 것은 to_csv 문서에 나와 있고, 그 문서에는 BOM이나 utf-8-sig 설명이 없습니다.
왜 파이썬에서는 정상인데 엑셀에서만 깨지나
pandas로 다시 읽으면 두 파일 모두 정상입니다.
[기본] read_csv 기본: ['이름', '품목', '금액'] ['김민준', '이서연']
[utf-8-sig] read_csv 기본: ['이름', '품목', '금액'] ['김민준', '이서연']
read_csv는 파일이 UTF-8이라고 가정하고 읽기 때문에 표시가 없어도 문제가 없습니다. 아래 Microsoft 문서가 BOM 유무로 여는 방법을 나누어 안내하는 것을 보면, 엑셀은 이 표시를 기준으로 삼는다고 볼 수 있습니다. 표시가 없는 UTF-8을 한국어 윈도우의 기본 글자표로 풀면 어떻게 되는지 파이썬으로 해석해 보았습니다.
cp949로 해석(오류 문자는 U+FFFD):
�씠由�,�뭹紐�,湲덉븸
源�誘쇱��,�궗怨�,12000
�씠�꽌�뿰,諛�,8500
strict 해석 결과: UnicodeDecodeError 'cp949' codec can't decode byte 0xec in position 0: illegal multibyte sequence
--- BOM 있는 파일을 UTF-8로 해석하면
이름,품목,금액
김민준,사과,12000
이서연,배,8500
숫자와 쉼표는 그대로이고 한글만 이상한 글자로 바뀌었습니다. 위 문자열은 파이썬 해석 결과이므로 엑셀 화면의 글자와 똑같지는 않을 수 있습니다. 다만 "숫자는 정상이고 한글만 깨진다"면 이 원인일 가능성이 큽니다. 파일 자체는 망가지지 않았으니 저장을 다시 하면 복구됩니다.
엑셀이 BOM을 어떻게 다루나
Microsoft 지원 문서 Opening CSV UTF-8 files correctly in Excel(영문 페이지)에 이렇게 적혀 있습니다.
You can open a CSV file encoded with UTF-8 normally if it was saved with BOM (Byte Order Mark). Otherwise, you can open it through either of the following ways.
BOM이 없을 때의 방법으로는 Data 탭의 Get Data > From file > Get Data from Text/CSV(Power Query)와 텍스트 가져오기 마법사(Get Data From Text)가 소개되어 있습니다. 한국어 엑셀에서는 데이터 탭에서 "텍스트/CSV"를 찾아 보시기 바랍니다. 이 문서는 BOM을 붙이는 방법은 설명하지 않으므로, 붙이는 쪽은 아래 파이썬 코드로 합니다.
이미 저장한 파일은 어떻게 하나
코드를 다시 돌릴 수 없는 파일이라면 파이썬으로 BOM만 붙일 수 있습니다. 아래 두 방법을 모두 실행해 첫 바이트와 다시 읽기를 확인했습니다.
# 방법 1: 바이트 앞에 BOM만 붙이기
out.write_bytes(b"\xef\xbb\xbf" + a.read_bytes())
# 방법 2: 읽은 뒤 utf-8-sig로 다시 저장하기
pd.read_csv(a).to_csv(out2, index=False, encoding="utf-8-sig")
변환 파일 첫 8바이트: ef bb bf ec 9d b4 eb a6
다시 읽기: ['김민준', '이서연'] ['이름', '품목', '금액']
read_csv 후 utf-8-sig 저장 첫 8바이트: ef bb bf ec 9d b4 eb a6
방법 1은 이미 BOM이 있는 파일에 쓰면 BOM이 두 번 붙으니 파일 첫 바이트가 ef bb bf가 아닐 때만 쓰시기 바랍니다.
cp949로 저장하는 방법은 어떤가
검색하면 encoding="cp949"로 저장하라는 답도 많이 나옵니다. 한글만 있는 표라면 됩니다.
cp949 저장 첫 16바이트: c0 cc b8 a7 2c c7 b0 b8 f1 2c b1 dd be d7 0d 0a
encoding='cp949'로 읽기: ['김민준', '이서연']
그러나 두 가지 불편이 있습니다. 첫째, 이 파일을 pandas 기본값으로 읽으면 오류가 납니다.
UnicodeDecodeError 'utf-8' codec can't decode byte 0xc0 in position 0: invalid start byte
이후 read_csv로 읽는 코드에는 encoding="cp949"를 같이 지정해야 합니다. 둘째, cp949에 없는 글자가 한 칸이라도 있으면 저장 단계에서 멈춥니다.
이모지 포함 cp949 저장: UnicodeEncodeError 'cp949' codec can't encode character '✅' in position 6: illegal multibyte sequence
이모지 포함 utf-8-sig 저장 후 다시 읽기: ['배송 완료 ✅']
utf-8-sig는 이모지가 있어도 저장되고 다시 읽을 때도 정상입니다. 이 오류 자체는 파이썬 UnicodeEncodeError: 'cp949' codec can't encode character, 출력을 파일로 보낼 때 해결법 글에서 따로 다뤘습니다. 다른 프로그램과 주고받을 파일이 아니라면 utf-8-sig가 더 덜 걸립니다.
utf-8-sig로 바꾼 뒤 알아 둘 점
utf-8-sig로 저장한 파일을 파이썬의 open()으로 직접 읽을 때는 encoding="utf-8"이면 맨 앞에 보이지 않는 글자(\ufeff)가 남습니다.
[utf-8-sig 파일] open(encoding='utf-8') 첫 글자 repr: '\ufeff'
[utf-8-sig 파일] open(encoding='utf-8-sig') 첫 글자 repr: '이'
그래서 첫 열 이름이 '\ufeff이름'이 되어 row["이름"]이 안 찾아질 수 있습니다. open()으로 읽을 때도 encoding="utf-8-sig"를 쓰면 BOM이 제거됩니다. 반면 pd.read_csv(파일)은 pandas 3.1.0에서 encoding="utf-8"을 지정해도 열 이름이 ['이름', '품목', '금액']으로 정상이었습니다.
정리
엑셀로 열어 볼 CSV는 to_csv(..., encoding="utf-8-sig")로 저장합니다. 파이썬이나 다른 프로그램이 읽을 파일이면 open()에도 utf-8-sig를 쓰거나 BOM을 처리하는 도구를 쓰시기 바랍니다. 이미 만든 파일은 앞에 BOM 3바이트를 붙이거나 pandas로 읽어 다시 저장하면 됩니다. 이렇게 했는데도 엑셀에서 깨진다면 위 Microsoft 문서의 텍스트/CSV 가져오기로 파일 원본 인코딩을 UTF-8로 지정해 여는 방법을 시험해 보시기 바랍니다.