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에서는 이 기본 인코딩이 <strong>Shift_JIS(코드페이지 932)</strong>입니다.
따라서 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은 <code>EF BB BF</code>(16진수)의 3바이트입니다.
Excel은 파일을 열 때 이 BOM을 감지하고 "이 파일은 UTF-8로 작성되었다"고 판단합니다. 이것이 <strong>BOM이 있는 UTF-8 CSV를 사용하면 Excel에서 문자 깨짐이 없는</strong> 이유입니다.
BOM이 있는 UTF-8과 BOM이 없는 UTF-8의 차이를 확인하려면, 텍스트 에디터나 <code>hexdump</code> 명령으로 파일의 처음 바이트를 확인합니다.
# 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」를 클릭합니다.
- 미리보기 화면이 표시됩니다. "파일 원본" 드롭다운에서 <strong>65001: Unicode (UTF-8)</strong>을 선택합니다.
- 구분 문자가 「쉼표」로 설정되어 있는지 확인한 후 「로드」를 클릭합니다.
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');
<code>fputcsv()</code>는 기본적으로 쉼표 구분과 큰따옴표 이스케이프를 사용합니다. 구분 기호를 변경하려면 세 번째 인수로 지정할 수 있습니다(예: 탭 구분은 <code>"\t"</code>).
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의 <code>open()</code>에서 <code>encoding='utf-8-sig'</code>를 지정하면 쓰기 시 BOM이 자동으로 추가되고 읽기 시 BOM이 자동으로 제거됩니다.
macOS 버전 Excel의 주의점
macOS 버전 Excel (Microsoft 365 for Mac)은 Windows 버전과 동작이 다른 부분이 있습니다.
- <strong>UTF-8 BOM이 있는 CSV</strong>: macOS 버전 Excel에서도 더블클릭으로 올바르게 열립니다 (비교적 최신 버전부터).
- <strong>구형 버전(Excel 2016 for Mac 등)</strong>: UTF-8 BOM이 있어도 문자 깨짐이 발생할 수 있습니다. 이 경우 "데이터 가져오기"를 사용하세요
- <strong>"다른 이름으로 저장"에서 CSV 선택</strong>: macOS 버전에서는 UTF-8 BOM 없이 저장될 수 있습니다. Windows 환경으로 전달하는 CSV는 재확인이 필요합니다.
- <strong>Numbers.app과의 혼용</strong>: macOS의 Numbers는 UTF-8을 표준으로 취급하지만 Excel과의 호환성에 주의가 필요합니다.
줄 바꿈 코드 문제 (<code>CRLF</code> vs <code>LF</code>)
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의 <code>fputcsv()</code>는 기본적으로 LF를 사용하지만, Windows 환경과의 호환성을 중시하는 경우 출력 후 <code>str_replace("\n", "\r\n", $output)</code>로 변환하는 것을 고려해 보세요.
// 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에서는 <code>fputs($output, "\xEF\xBB\xBF")</code>로 BOM을 맨 앞에 추가합니다
- Python에서 <code>encoding='utf-8-sig'</code>를 지정합니다.
- 받은 CSV의 문자 인코딩을 확인하려면 <code>chardet</code> 또는 <code>file</code> 명령을 사용하세요
- Excel 사용자에게 BOM 없는 UTF-8 CSV를 제공할 때는 "데이터 가져오기" 프로세스를 안내합니다.
- 줄 바꿈 코드는 <code>CRLF</code>가 RFC 표준이지만, 현대의 Excel은 <code>LF</code>도 허용합니다.
- macOS 버전 Excel은 동작이 다를 수 있으므로 수신 측에서의 동작 확인을 권장합니다
이 기사에서 사용할 수 있는 테스트 파일
- <a href="/ja/files/encoding/" class="text-primary-600 dark:text-primary-400 hover:underline">문자 인코딩 테스트 파일 목록</a> — UTF-8 BOM 포함/미포함, Shift_JIS 등 다양한 인코딩 샘플
- <a href="/ja/files/csv/" class="text-primary-600 dark:text-primary-400 hover:underline">테스트용 CSV 파일 목록</a> — 줄바꿈 코드·문자 코드·BOM 유무의 조합 샘플
- <a href="/ja/files/newline/" class="text-primary-600 dark:text-primary-400 hover:underline">줄바꿈 코드 테스트 파일 목록</a> — CRLF / LF / CR 각 패턴 확인
관련 기사
- <a href="/ja/blog/csv-encoding-trouble-guide/" class="text-primary-600 dark:text-primary-400 hover:underline">CSV 문자 인코딩 문제 완전히 해결하기! 문자 코드, BOM, 줄 바꿈의 기초 지식</a>
- <a href="/ja/blog/file-format-quick-reference/" class="text-primary-600 dark:text-primary-400 hover:underline">개발자를 위한 파일 형식 빠른 참조</a>
- <a href="/ja/blog/base64-size-increase/" class="text-primary-600 dark:text-primary-400 hover:underline">Base64 인코딩이 파일 크기를 33% 증가시키는 이유</a>