Excel에서 CSV를 올바르게 열고 저장하는 방법|문자 깨짐 방지 UTF-8 BOM 대응
「시스템에서 내보낸 CSV 파일을 Excel로 열었는데 일본어가 깨졌다」——이 문제는 웹 개발, 데이터 분석, 업무 시스템 현장에서 일상적으로 발생하는 문제입니다. 대부분의 원인은 UTF-8과 Shift_JIS(CP932)의 문자 인코딩 차이에 있습니다. 본 기사에서는 문자 깨짐의 근본 원인부터 시작하여 UTF-8 BOM이 포함된 CSV를 이용한 해결 방법, Excel의 가져오기 절차, PHP·Python 구현 방법, 줄바꿈 코드 문제까지 체계적으로 설명합니다.
Excel이 기본적으로 Shift_JIS를 기대하는 문제
Windows 버전의 Excel에서 CSV 파일을 더블클릭으로 열 때, OS의 로케일 설정을 기반으로 인코딩을 자동으로 감지합니다. 일본어 Windows에서는 이 기본 인코딩이 Shift_JIS(코드페이지 932)입니다.
따라서 UTF-8로 저장된 CSV를 더블클릭으로 그대로 열면 멀티바이트 문자(일본어 등)가 올바르게 해석되지 않아 문자가 깨집니다. 「엔지니어가 웹 시스템에서 생성한 UTF-8 CSV를 엔지니어가 아닌 담당자가 Excel로 열었을 때 문자가 깨진다」는 문제가 반복적으로 발생하는 이유가 바로 이것입니다.
문자 인코딩의 대응 관계를 정리하면 다음과 같습니다.
| 문자 인코딩 | 별칭 | Excel에서의 처리 | 웹·Linux에서의 취급 |
|---|---|---|---|
| Shift_JIS | CP932、Windows-31J | 일본어 버전에서 표준 | 레거시. 사용 비권장 |
| UTF-8 (BOM 없음) | — | 문자가 깨짐 (이전 버전) | 웹 표준 |
| UTF-8 BOM 포함 | UTF-8 with BOM | 올바르게 인식됨 | BOM이 불필요한 문자로 문제가 될 수 있음 |
| UTF-16 LE BOM 포함 | — | 올바르게 인식됨 | 잘 사용되지 않음 |
UTF-8 BOM 포함 CSV에서 문자 깨짐 방지 방법
BOM(Byte Order Mark)은 파일의 시작에 추가되는 특수한 바이트 시퀀스로, 텍스트 파일의 문자 코드를 식별하기 위한 시그니처입니다. UTF-8 BOM은 EF BB BF(16진수)의 3바이트입니다.
Excel은 파일을 열 때 이 BOM을 감지하고 "이 파일은 UTF-8로 작성되었다"고 판단합니다. 이것이 BOM이 있는 UTF-8 CSV를 사용하면 Excel에서 문자 깨짐이 없는 이유입니다.
BOM이 있는 UTF-8과 BOM이 없는 UTF-8의 차이를 확인하려면, 텍스트 에디터나 hexdump 명령으로 파일의 처음 바이트를 확인합니다.
# Linux / macOS での確認
hexdump -C sample.csv | head -1
# BOM なし UTF-8 の出力例:
# 00000000 e5 90 8d e5 89 8d 2c e5 ...
# BOM 付き UTF-8 の出力例:
# 00000000 ef bb bf e5 90 8d e5 89 ...(先頭に ef bb bf)
Excel에서 "데이터" → "텍스트 파일"에서 가져오기 단계
BOM이 없는 UTF-8 CSV 파일을 Excel에서 올바르게 열려면, 더블 클릭하지 말고 "데이터 가져오기" 기능을 사용합니다. 버전에 따라 단계가 다르지만, Excel 2016 이상(Microsoft 365 포함)에서는 다음 절차를 사용할 수 있습니다.
- Excel을 시작하고 새 통합 문서를 엽니다.
- 「데이터」탭 → 「텍스트 또는 CSV에서」를 클릭합니다.
- 대상 CSV 파일을 선택하고 「Import」를 클릭합니다.
- 미리보기 화면이 표시됩니다. "파일 원본" 드롭다운에서 65001: Unicode (UTF-8)을 선택합니다.
- 구분 문자가 「쉼표」로 설정되어 있는지 확인한 후 「로드」를 클릭합니다.
Excel 2013 이전의 경우 "데이터" → "외부 데이터 가져오기" → "텍스트 파일"을 사용하고, "텍스트 파일 마법사"의 2단계에서 문자 인코딩을 UTF-8(65001)로 변경합니다.
PHP에서 UTF-8 BOM이 있는 CSV를 출력하는 코드
웹 시스템에서 CSV를 다운로드하도록 할 경우, PHP에서 BOM이 있는 UTF-8 CSV를 출력하는 것이 가장 간단한 해결 방법입니다.
// UTF-8 BOM 付き CSV のダウンロード出力
function outputCsvWithBom(array $headers, array $rows, string $filename = 'export.csv'): void
{
// キャッシュ無効化・ダウンロードヘッダーを設定
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Cache-Control: no-cache, no-store, must-revalidate');
$output = fopen('php://output', 'w');
// UTF-8 BOM を出力(EF BB BF)
fputs($output, "\xEF\xBB\xBF");
// ヘッダー行を出力
fputcsv($output, $headers);
// データ行を出力
foreach ($rows as $row) {
fputcsv($output, $row);
}
fclose($output);
exit;
}
// 使用例
$headers = ['名前', 'メールアドレス', '登録日'];
$rows = [
['山田 太郎', 'taro@example.com', '2026-04-14'],
['鈴木 花子', 'hanako@example.com', '2026-04-13'],
];
outputCsvWithBom($headers, $rows, 'users_' . date('Ymd') . '.csv');
fputcsv()는 기본적으로 쉼표 구분과 큰따옴표 이스케이프를 사용합니다. 구분 기호를 변경하려면 세 번째 인수로 지정할 수 있습니다(예: 탭 구분은 "\t").
Python에서의 문자 인코딩 변환 코드
기존 CSV 파일의 문자 인코딩을 변환하거나 Shift_JIS로 전달된 데이터를 UTF-8로 변환할 때 Python이 편리합니다.
import csv
import codecs
# BOM なし UTF-8 → BOM 付き UTF-8 に変換して保存
def add_bom_to_utf8_csv(input_path: str, output_path: str) -> None:
with open(input_path, 'r', encoding='utf-8') as infile:
content = infile.read()
with open(output_path, 'w', encoding='utf-8-sig') as outfile:
# 'utf-8-sig' は自動的に BOM を付加する
outfile.write(content)
# Shift_JIS CSV → UTF-8 BOM 付き CSV に変換
def convert_sjis_to_utf8_bom(input_path: str, output_path: str) -> None:
with open(input_path, 'r', encoding='shift_jis', errors='replace') as infile:
rows = list(csv.reader(infile))
with open(output_path, 'w', encoding='utf-8-sig', newline='') as outfile:
writer = csv.writer(outfile)
writer.writerows(rows)
# 文字コードを自動検出して変換(chardet を使用)
# pip install chardet
import chardet
def detect_and_convert(input_path: str, output_path: str) -> None:
with open(input_path, 'rb') as f:
raw_data = f.read()
detected = chardet.detect(raw_data)
encoding = detected['encoding'] or 'utf-8'
print(f'検出した文字コード: {encoding}(信頼度: {detected["confidence"]*100:.0f}%)')
content = raw_data.decode(encoding, errors='replace')
with open(output_path, 'w', encoding='utf-8-sig', newline='') as outfile:
outfile.write(content)
Python의 open()에서 encoding='utf-8-sig'를 지정하면 쓰기 시 BOM이 자동으로 추가되고 읽기 시 BOM이 자동으로 제거됩니다.
macOS 버전 Excel의 주의점
macOS 버전 Excel (Microsoft 365 for Mac)은 Windows 버전과 동작이 다른 부분이 있습니다.
- UTF-8 BOM이 있는 CSV: macOS 버전 Excel에서도 더블클릭으로 올바르게 열립니다 (비교적 최신 버전부터).
- 구형 버전(Excel 2016 for Mac 등): UTF-8 BOM이 있어도 문자 깨짐이 발생할 수 있습니다. 이 경우 "데이터 가져오기"를 사용하세요
- "다른 이름으로 저장"에서 CSV 선택: macOS 버전에서는 UTF-8 BOM 없이 저장될 수 있습니다. Windows 환경으로 전달하는 CSV는 재확인이 필요합니다.
- Numbers.app과의 혼용: macOS의 Numbers는 UTF-8을 표준으로 취급하지만 Excel과의 호환성에 주의가 필요합니다.
줄 바꿈 코드 문제 (CRLF vs LF)
CSV의 문자 깨짐과 함께 줄바꿈 코드의 차이도 문제가 될 수 있습니다.
| 줄 바꿈 코드 | 바이트 열 | 주요 환경 | CSV에서의 처리 |
|---|---|---|---|
| CRLF | 0D 0A |
Windows、HTTP | RFC 4180에서 정의된 표준 |
| LF | 0A |
Linux, macOS, Git 기본값 | 대부분의 경우 Excel에서도 작동함 |
| CR | 0D |
구형 macOS (9 이전) | 오래된 Excel에서만 문제가 될 수 있습니다 |
CSV의 표준 사양인 RFC 4180에서는 CRLF가 규정되어 있지만, 현대의 Excel은 LF만 있는 CSV도 문제없이 처리합니다. 그러나 필드 내에 줄바꿈을 포함하는 CSV에서는 줄바꿈 코드의 처리가 중요해집니다. PHP의 fputcsv()는 기본적으로 LF를 사용하지만, Windows 환경과의 호환성을 중시하는 경우 출력 후 str_replace("\n", "\r\n", $output)로 변환하는 것을 고려해 보세요.
// PHP で CRLF 改行の CSV を出力する方法
function outputCsvCrlfWithBom(array $headers, array $rows, string $filename = 'export.csv'): void
{
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
// 一旦バッファに書き出して CRLF に変換する
ob_start();
$output = fopen('php://output', 'w');
fputs($output, "\xEF\xBB\xBF");
fputcsv($output, $headers);
foreach ($rows as $row) {
fputcsv($output, $row);
}
fclose($output);
$csv = ob_get_clean();
// LF を CRLF に変換(すでに CRLF になっているものは除外)
$csv = str_replace(["\r\n", "\n"], "\r\n", $csv);
echo $csv;
exit;
}
요약: 문자 깨짐이 없는 CSV 배포의 모범 사례
- Excel 사용자에게 배포하는 CSV는 UTF-8 BOM 포함하여 출력
- PHP에서는
fputs($output, "\xEF\xBB\xBF")로 BOM을 맨 앞에 추가합니다 - Python에서
encoding='utf-8-sig'를 지정합니다. - 받은 CSV의 문자 인코딩을 확인하려면
chardet또는file명령을 사용하세요 - Excel 사용자에게 BOM 없는 UTF-8 CSV를 제공할 때는 "데이터 가져오기" 프로세스를 안내합니다.
- 줄 바꿈 코드는
CRLF가 RFC 표준이지만, 현대의 Excel은LF도 허용합니다. - macOS 버전 Excel은 동작이 다를 수 있으므로 수신 측에서의 동작 확인을 권장합니다
이 기사에서 사용할 수 있는 테스트 파일
- 문자 인코딩 테스트 파일 목록 — UTF-8 BOM 포함/미포함, Shift_JIS 등 다양한 인코딩 샘플
- 테스트용 CSV 파일 목록 — 줄바꿈 코드·문자 코드·BOM 유무의 조합 샘플
- 줄바꿈 코드 테스트 파일 목록 — CRLF / LF / CR 각 패턴 확인