CSV는 보기보다 단순하지만, 생각보다 까다롭습니다
CSV 파일은 결국 텍스트일 뿐입니다. 줄바꿈으로 행을 나누고, 쉼표로 필드를 구분하죠. 개념만 보면 이게 전부입니다. 그런데 막상 실무에서 다뤄 보면 쉼표가 들어간 따옴표 문자열, 필드 안에 개행이 포함된 데이터, 지역별로 다른 구분자 관례(쉼표냐 세미콜론이냐), 엑셀로 저장했더니 BOM이 붙어 있는 파일 등 예외 상황이 줄줄이 튀어나옵니다. 그래서 직접 line.split(",")으로 파싱하려 드는 건 거의 항상 잘못된 선택입니다.
다행히 파이썬의 내장 csv 모듈이 이런 골치 아픈 부분을 모두 처리해 줍니다. 중소 규모 파일이라면 이 모듈 하나로 충분한 경우가 대부분이에요.
csv.reader로 CSV 파일 읽기
csv.reader는 각 행을 문자열 리스트 형태로 하나씩 돌려줍니다:
import csv
with open("people.csv", newline="") as f:
reader = csv.reader(f)
for row in reader:
print(row)
몇 가지 놓치기 쉬운 포인트를 정리해 두자.
open에 항상newline=""를 넘기자. 줄바꿈 처리는 csv 모듈이 알아서 해준다. 이걸 빼먹으면 Windows에서 빈 줄이 한 줄씩 끼어 들어간다.- 모든 값은 문자열이다.
"42"는int(...)로 감싸기 전까진 그냥 문자열일 뿐이다. CSV 자체에는 타입 개념이 없다. - 헤더 행도 그냥 평범한 한 줄이다. 헤더가 있는 파일이라면 첫 줄을 직접 건너뛰거나
DictReader로 바꿔서 읽어야 한다.
헤더 행 건너뛰기
import csv
with open("people.csv", newline="") as f:
reader = csv.reader(f)
headers = next(reader) # pulls the first row out
for row in reader:
print(row)
next(reader)는 이터레이터를 한 칸 앞으로 옮기면서 해당 행을 반환합니다.
DictReader로 딕셔너리처럼 읽기
csv.DictReader를 쓰면 첫 번째 행을 헤더로 인식하고, 그 뒤에 오는 각 행을 딕셔너리 형태로 돌려줍니다.
import csv
with open("people.csv", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["email"])
대부분의 경우 이 방식이 정답입니다. 컬럼 이름 자체가 문서 역할을 해주고, 원본 파일에서 컬럼 순서가 바뀌어도 코드가 깨지지 않으니까요.
헤더가 없는 파일이라면 fieldnames=["name", "email", ...]처럼 직접 지정해서 넘겨주면 됩니다.
csv.writer로 CSV 파일 쓰기
csv.writer는 리스트 형태의 행(row)을 CSV 한 줄로 변환해 줍니다:
import csv
rows = [
["name", "age", "city"],
["Rosa", 30, "Lisbon"],
["Ada", 36, "London"],
]
with open("out.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)
writerow(row)는 한 줄만 쓰고, writerows(rows)는 반복 가능한 데이터 전체를 한 번에 기록합니다. 두 메서드 모두 필드 안에 쉼표, 따옴표, 줄바꿈이 들어 있으면 알아서 따옴표로 감싸주기 때문에 별도로 신경 쓸 필요가 없습니다.
DictWriter로 딕셔너리 쓰기
데이터가 이미 딕셔너리 형태라면 굳이 리스트로 변환할 필요 없이 DictWriter를 바로 쓰면 됩니다:
import csv
people = [
{"name": "Rosa", "age": 30, "city": "Lisbon"},
{"name": "Ada", "age": 36, "city": "London"},
]
with open("out.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "age", "city"])
writer.writeheader()
writer.writerows(people)
fieldnames 인자는 헤더와 컬럼 순서를 모두 결정합니다. 딕셔너리에 있지만 fieldnames에 포함되지 않은 키는 조용히 무시됩니다. 에러를 발생시키고 싶다면 extrasaction="raise"를 지정하면 됩니다.
구분자와 따옴표 처리 바꾸기
모든 "CSV" 파일이 쉼표를 쓰는 건 아닙니다. 유럽권에서는 ;를 자주 쓰고, 탭으로 구분된 파일은 \t, 어떤 시스템은 |를 쓰기도 하죠. 이럴 때는 delimiter= 인자로 넘겨주면 됩니다:
import csv
with open("data.tsv", newline="") as f:
reader = csv.reader(f, delimiter="\t")
for row in reader:
print(row)
따옴표 처리 규칙이 독특한 파일이라면 csv.register_dialect(...)로 한 번 설정해 두고 재사용할 수 있습니다. 하지만 대부분의 파일은 기본값에 delimiter=만 지정해 줘도 충분합니다.
인코딩 처리
CSV는 결국 텍스트 파일이기 때문에 인코딩을 피할 수 없습니다. 요즘은 UTF-8이 사실상 표준이지만, Windows에서 엑셀로 만든 파일은 cp1252를 쓰거나 UTF-8 BOM이 붙어 있는 경우가 종종 있습니다. 그러니 인코딩은 명시적으로 지정해 주는 게 좋습니다.
with open("data.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
...
UnicodeDecodeError가 뜬다면 예상한 인코딩이 아니라는 뜻입니다. 보통 utf-8-sig(엑셀 BOM 처리용), cp1252, latin-1 이 세 가지가 범인인 경우가 많으니 하나씩 시도해 보세요.
CSV 데이터를 쓸 만한 타입으로 변환하기
CSV에서 읽어 온 값은 전부 문자열로 들어오기 때문에, 타입 변환은 직접 처리해 줘야 합니다:
(StringIO를 쓰면 실제 파일 없이도 예제를 돌려볼 수 있어요. 실제 코드에서는 open(path)을 쓰면 됩니다.)
날짜, nullable 숫자, 열 가지 방식으로 표기되는 true/false 같은 까다로운 타입이 섞인 CSV라면 pandas를 쓰는 게 낫습니다. 이런 경우에 대한 처리 규칙이 대부분 기본으로 들어 있거든요.
pandas를 써야 할 때
pandas.read_csv(path)는 DataFrame을 돌려주는데, 아래와 같은 작업이 필요한 순간부터는 DataFrame이 제격입니다:
- 행 필터링:
df[df["active"] == True] - 집계:
df.groupby("city")["age"].mean() - 다른 테이블과 조인
- 간단한 포맷을 적용해서 다시 파일로 저장
import pandas as pd
df = pd.read_csv("people.csv")
adults = df[df["age"] >= 18]
adults.to_csv("adults.csv", index=False)
Pandas는 작고 단순한 읽기 작업에는 과한 편이고, 의존성 부담도 큽니다(가상 환경에 pip으로 설치해야 합니다). 그래도 데이터 형태의 작업이라면 파이썬 분석가들이 가장 먼저 손이 가는 도구죠.
대용량 파일 스트리밍 처리하기
csv.reader는 이미 지연 평가(lazy) 방식이라 한 줄씩 읽어옵니다. 처음부터 list(reader)로 전부 불러오지 말고 반복문으로 하나씩 돌리기만 하면, 파일 크기와 상관없이 메모리 사용량은 일정하게 유지됩니다:
import csv
with open("huge.csv", newline="") as f:
reader = csv.DictReader(f)
error_count = 0
for row in reader:
if row["status"] == "error":
error_count += 1
print(f"Found {error_count} errors.")
10GB짜리 파일이든 10KB짜리 파일이든 똑같이 잘 처리됩니다. 단, 행들을 리스트에 쌓아두지만 않는다면요.
몇 가지 습관
- CSV를 읽거나 쓸 때는
open에 항상newline=""을 넘겨주세요. - 헤더가 있는 파일이라면
DictReader/DictWriter를 쓰세요. 정수 인덱스보다 훨씬 읽기 좋습니다. - 특히 엑셀에서 저장했거나 비영어권에서 온 파일은 인코딩을 명시적으로 지정하세요.
- 타입 변환은 읽는 단계에서 바로 처리해서, 이후 코드가 신경 쓰지 않도록 하세요.
- 데이터를 단순히 옮기는 게 아니라 분석하고 싶어진다면 그때는 pandas를 꺼내 쓰면 됩니다.
다음 순서
이제 JSON과 CSV를 다룰 줄 알게 됐습니다. 실전에서 마지막으로 다룰 기술은 네트워크로 데이터를 가져오는 것입니다. 다음 문서에서 requests 라이브러리로 HTTP 요청을 보내는 방법을 살펴보겠습니다.
자주 묻는 질문
파이썬에서 CSV 파일은 어떻게 읽나요?
기본 내장 모듈인 csv를 쓰면 됩니다. csv.reader는 각 행을 문자열 리스트로 돌려주고, csv.DictReader는 첫 줄을 헤더로 인식해서 각 행을 딕셔너리로 만들어줘요. 파일을 열 때는 반드시 newline='' 옵션을 붙여야 줄바꿈이 깨지지 않습니다: with open('data.csv', newline='') as f:.
파이썬에서 CSV 파일은 어떻게 쓰나요?
쓰기 모드로 연 파일(옵션으로 newline='' 지정)에 csv.writer를 붙여주면 됩니다. 행 하나씩 쓸 땐 writer.writerow([...]), 여러 행을 한 번에 쓸 땐 writer.writerows([[...], [...]])를 쓰세요. 딕셔너리 형태의 데이터라면 csv.DictWriter가 편합니다 — 헤더도 알아서 처리해줘요.
csv 모듈과 pandas 중 뭘 써야 하나요?
간단히 읽고 쓰거나, 한 행씩 순차 처리하거나, 외부 라이브러리 의존성을 늘리고 싶지 않다면 csv 모듈이면 충분합니다. 반대로 필터링·그룹화·조인이 필요하거나, 파일이 커서 벡터 연산이 중요한 상황이라면 pandas가 훨씬 유리해요. 둘 다 같은 파일을 다루니, 결국 '불러온 뒤에 뭘 할 건가'에 따라 고르면 됩니다.
윈도우에서 CSV를 쓰면 행 사이에 빈 줄이 생기는데 왜 그런가요?
newline='' 옵션 없이 파일을 열어서 그렇습니다. csv 모듈은 자체적으로 줄 끝 문자를 붙이는데, 이 옵션이 없으면 윈도우에서 줄바꿈이 이중으로 들어가거든요. CSV 파일을 열 때는 읽기든 쓰기든 항상 open(path, newline='') 형태로 여는 습관을 들이세요.