Справочник · Данные и форматы
CSV без сюрпризов: кавычки, разделители и Excel
Правила формата по RFC 4180, почему Excel ломает кириллицу и длинные числа, и как читать и писать CSV в командной строке, Python и SQL.
CSV выглядит самым простым форматом на свете: значения через запятую, строки через перевод строки. На практике это источник половины проблем с выгрузками. Единого стандарта долго не было, и каждая программа понимала формат по-своему. Ближе всего к общему знаменателю RFC 4180, на нём и стоит строить свои файлы.
Правила формата
id,name,city,comment
1,Анна,Казань,
2,"Петров, Иван",Москва,"Сказал: ""Перезвоню"""
3,Ольга,Пермь,"Первая строка
вторая строка"- Записи разделяются переводом строки. RFC предписывает
CRLF, но почти все программы читают иLF. - Поля разделяются запятой. Пробелы вокруг запятой — часть значения.
- Поле, где есть запятая, кавычка или перевод строки, заключается в двойные кавычки.
- Кавычка внутри такого поля удваивается:
"". - Первая строка может быть заголовком с именами столбцов.
- Во всех строках одинаковое число полей.
Разделители и локали
| Разделитель | Где встречается |
|---|---|
, | стандарт, англоязычные системы, большинство выгрузок |
; | Excel в русской, немецкой и других локалях с десятичной запятой |
\t | TSV: удобно, когда в данных много запятых |
| | старые банковские и учётные выгрузки |
Excel в русской Windows сохраняет CSV с точкой с запятой, потому что запятая занята под дробную часть: 3,14. Такой файл на англоязычной машине откроется одним столбцом. Подсказку для Excel можно поставить первой строкой файла: sep=;. Остальные программы эту строку не понимают и посчитают данными.
Excel и кодировка
Excel открывает CSV по двойному щелчку в системной кодировке, в русской Windows это Windows-1251. Файл в UTF-8 без метки превращается в кракозябры вида РђРЅРЅР°. Решения:
- записывать UTF-8 с BOM, в Python это кодировка
utf-8-sig; - открывать через «Данные → Из текстового/CSV-файла» и выбирать кодировку 65001 (UTF-8);
- отдавать пользователям XLSX, если файл предназначен именно для Excel.
Подробнее про BOM и кракозябры — в статье о кодировках.
Что Excel делает с данными
При открытии Excel угадывает типы и меняет значения без спроса:
| Было в CSV | Стало в Excel |
|---|---|
00123 | 123: ведущие нули пропали |
1234567890123456789 | 1,23457E+18, точность после 15 цифр потеряна |
1-2, MARCH1 | дата |
+79001234567 | число без плюса |
=SUM(A1:A9) | выполненная формула |
Для номеров, артикулов и почтовых индексов это значит: импортировать столбец как текст через мастер импорта. Последний пункт — ещё и угроза безопасности.
Командная строка
Для простых файлов без кавычек хватает стандартных утилит:
head -n 1 data.csv | tr ',' '\n' | nl # пронумеровать столбцы
cut -d, -f1,3 data.csv # взять 1-й и 3-й столбцы
tail -n +2 data.csv | sort -t, -k3,3 # без заголовка, сортировка по 3-му
awk -F, 'NR > 1 { s += $4 } END { print s }' data.csv # сумма 4-го столбца
column -s, -t < data.csv | less -S # посмотреть таблицейДля настоящих CSV есть утилиты, которые понимают кавычки: csvkit (csvcut, csvgrep, csvstat, csvsql), xsv и его продолжение qsv, miller:
csvcut -c email,city users.csv
csvgrep -c city -m "Казань" users.csv
mlr --icsv --ojson cat users.csv # CSV в JSON
mlr --icsv --opprint sort -nr total orders.csv # таблицей, по убыванию суммыPython
Модуль csv из стандартной библиотеки всё делает правильно, если открыть файл с newline="":
import csv
with open("users.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
print(row["email"], row["city"])
with open("out.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.DictWriter(f, fieldnames=["email", "city"], delimiter=";")
w.writeheader()
w.writerow({"email": "anna@example.com", "city": "Казань, Татарстан"})Без newline="" на Windows между строками появятся пустые, а переводы строк внутри полей сломаются. Разделитель неизвестного файла можно угадать: csv.Sniffer().sniff(f.read(4096)).delimiter.
В pandas: pd.read_csv("data.csv", sep=";", decimal=",", dtype={"inn": str}, encoding="cp1251"). Ключ dtype с str сохраняет ведущие нули.
SQL
-- PostgreSQL, на стороне клиента psql
\copy orders TO 'orders.csv' WITH (FORMAT csv, HEADER)
\copy orders FROM 'orders.csv' WITH (FORMAT csv, HEADER, DELIMITER ';')sqlite3 -header -csv shop.db "SELECT * FROM orders" > orders.csv
sqlite3 shop.db ".import --csv orders.csv orders"Как выпускать хороший CSV
- UTF-8, если файл для Excel — с BOM.
- Заголовок в первой строке, имена без пробелов и повторов.
- Даты в формате ISO 8601:
2026-11-03или2026-11-03T14:05:00Z, без локальных форматов. - Десятичный разделитель — точка, без разделителей тысяч.
- Пустое значение — пустое поле, а не
NULL,-илин/д. - Все поля с запятыми, кавычками и переводами строк — в кавычках, а лучше пусть это сделает библиотека.
- Если данные вложенные, подумайте о JSON Lines: по объекту JSON на строку, без проблем с экранированием.
Сравнить две выгрузки построчно удобно в инструменте сравнения текстов.