LabHub
배우기 러닝패스 코스

고객 데이터 다루기 · 규격을 안 지킨 CSV · 이론

줄 수를 세면 틀린다 — CSV 는 생각보다 어렵다

LabHub 에서 이어서 보기

한 줄 요약

CSV 에서 줄(line)과 레코드(record)는 다른 것이고, 이 둘을 같다고 믿는 순간 행 수도 금액도 조용히 틀린다.

왜 이게 필요했나

고객이 준 매출 파일을 열어 wc -l 을 쳤더니 431 이 나왔다. 그런데 고객은 402건이라고 한다. 29건 차이의 정체를 찾느라 반나절이 간다.

차이는 대개 두 가지에서 온다. 하나는 인용 안의 줄바꿈이다. 주문 메모에 줄을 나눠 적으면 그 레코드는 파일에서 두 줄을 차지한다. 다른 하나는 인용 안의 구분자다. 상호가 "문샵, 주식회사" 라면 그 줄을 쉼표로 자를 때 칼럼이 하나 늘어난다. 금액 칸이 한 칸 밀리므로 그 줄의 금액은 합계에서 빠지거나 엉뚱한 값이 더해진다.

무서운 것은 오류가 안 난다는 점이다. 파서는 아무 말 없이 숫자를 내놓고, 그 숫자는 그럴듯하게 생겼다. 이런 종류의 실수는 고객이 "우리 매출이 이것보다 많은데요" 라고 말해 줄 때까지 살아남는다.

어떻게 동작하나

[RFC 4180](https://www.rfc-editor.org/rfc/rfc4180.html)이 CSV 의 공통 규칙을 적어 두었다. 핵심은 네 줄이다.

여기서 실무가 겪는 문제 세 가지가 곧바로 나온다.

첫째, 이 규격은 권고이고 세상은 다 지키지 않는다. RFC 4180 자신도 첫머리에서 CSV 가 오랫동안 공식 규격 없이 쓰여 왔고 이 문서는 널리 쓰이는 형태를 적어 둔 것이라고 밝힌다. 실제로 오는 파일은 구분자가 세미콜론이거나 탭이고, 줄 끝이 LF 이거나 한 파일 안에서 섞여 있고, 머리글이 없다.

둘째, 머리글 유무는 파일만 보고는 알 수 없다. 그래서 추정해야 한다. 첫 줄에는 숫자가 없는데 뒷줄에는 있다는 것이 쓸 만한 잣대다. 다만 이것은 잣대이지 보증이 아니다 — 첫 데이터 줄이 통째로 문자열인 자료에서는 틀린다.

셋째, 손으로 자르면 안 된다. line.split(",") 은 인용을 모른다. 파이썬 [csv 모듈](https://docs.python.org/3/library/csv.html)은 인용 안의 구분자와 줄바꿈, 두 개로 쓴 따옴표를 전부 처리한다. 그리고 파일을 열 때 newline="" 을 줘야 한다 — 문서가 명시하는 요구 사항이고, 이것을 빠뜨리면 인용 안의 줄바꿈이 레코드를 끊어 버린다.

원본 한 레코드  A-1002,"달빛상사","고객 요청:  오전 배송",1,12000split(",") 의 눈에는          csv 모듈의 눈에는  줄 2개 · 필드 3개와 3개       레코드 1개 · 필드 5개

현장에서 만나는 모습

첫째, 닫히지 않은 따옴표. 어느 레코드에서 따옴표를 하나 빼먹으면 그 지점부터 파일 끝까지가 한 필드가 된다. 파서는 오류를 내지 않고, 결과는 "마지막 한 건" 으로 보인다. 이것은 파일이 인용 상태로 끝났는지 한 번 훑어보면 확실히 알 수 있다 — 따옴표를 세되 두 개로 쓴 것("")은 건너뛰면 된다.

둘째, 엑셀이 바꿔 놓는다. 사람이 엑셀로 열었다 저장하면 구분자가 지역 설정을 따라 세미콜론이 되고, 긴 숫자는 지수 표기가 되고, 앞의 0 이 날아간다. 그래서 "원본 파일" 은 사람이 열기 전의 것이어야 한다.

셋째, 깨진 줄을 그냥 버린다. 칼럼 수가 안 맞는 줄을 조용히 건너뛰면 그 건수만큼 매출이 사라지고, 나중에 설명할 근거가 없다. 못 쓰는 줄은 거절 파일로 떼어 내고 원래 줄 번호를 함께 적는다. 그래야 고객이 자기 파일에서 그 줄을 열어 볼 수 있다.

넷째, 파일 전체를 거절할 것인가 줄만 뺄 것인가. 은행 수신 파일처럼 상대의 합계와 우리 원장이 맞아야 하는 자리에서는 한 줄만 어긋나도 파일 전체를 돌려보낸다. 반대로 고객이 준 분석용 자료는 돌려보낼 곳이 없다 — 쓸 수 있는 줄은 쓰고 못 쓰는 줄만 떼어 낸다. 어느 쪽이 옳은지는 자료가 아니라 업무가 정한다.

실무에서 진짜 중요한 것

다음 실습에서 할 것

같은 주문 20건을 일곱 가지 모양으로 내보내는 피드를 직접 만든 뒤, 문지기 csvgate.py 를 한 단계씩 키운다. 먼저 손으로 자른 파서의 답을 내고, csv 모듈의 답과 나란히 놓아 얼마나 벌어지는지를 숫자로 본다. 그다음 줄 끝과 구분자와 머리글 유무를 판별하고, 칼럼 수가 어긋난 줄과 빈 줄과 닫히지 않은 따옴표를 거절 파일로 떼어 낸다. 채점기는 매번 다른 상호와 금액으로 자기 피드를 만들어 여러분의 문지기를 실제로 돌리고 답을 대조한다.