Лаборатория кодера

Справочник · Данные и форматы

CSV без сюрпризов: кавычки, разделители и Excel

Правила формата по RFC 4180, почему Excel ломает кириллицу и длинные числа, и как читать и писать CSV в командной строке, Python и SQL.

Обновлено · 5 мин чтения

CSV выглядит самым простым форматом на свете: значения через запятую, строки через перевод строки. На практике это источник половины проблем с выгрузками. Единого стандарта долго не было, и каждая программа понимала формат по-своему. Ближе всего к общему знаменателю RFC 4180, на нём и стоит строить свои файлы.

Правила формата

text
id,name,city,comment
1,Анна,Казань,
2,"Петров, Иван",Москва,"Сказал: ""Перезвоню"""
3,Ольга,Пермь,"Первая строка
вторая строка"
  1. Записи разделяются переводом строки. RFC предписывает CRLF, но почти все программы читают и LF.
  2. Поля разделяются запятой. Пробелы вокруг запятой — часть значения.
  3. Поле, где есть запятая, кавычка или перевод строки, заключается в двойные кавычки.
  4. Кавычка внутри такого поля удваивается: "".
  5. Первая строка может быть заголовком с именами столбцов.
  6. Во всех строках одинаковое число полей.

Разделители и локали

РазделительГде встречается
,стандарт, англоязычные системы, большинство выгрузок
;Excel в русской, немецкой и других локалях с десятичной запятой
\tTSV: удобно, когда в данных много запятых
|старые банковские и учётные выгрузки

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
00123123: ведущие нули пропали
12345678901234567891,23457E+18, точность после 15 цифр потеряна
1-2, MARCH1дата
+79001234567число без плюса
=SUM(A1:A9)выполненная формула

Для номеров, артикулов и почтовых индексов это значит: импортировать столбец как текст через мастер импорта. Последний пункт — ещё и угроза безопасности.

Командная строка

Для простых файлов без кавычек хватает стандартных утилит:

bash
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:

bash
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="":

python
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

sql
-- PostgreSQL, на стороне клиента psql
\copy orders TO 'orders.csv' WITH (FORMAT csv, HEADER)
\copy orders FROM 'orders.csv' WITH (FORMAT csv, HEADER, DELIMITER ';')
bash
sqlite3 -header -csv shop.db "SELECT * FROM orders" > orders.csv
sqlite3 shop.db ".import --csv orders.csv orders"

Как выпускать хороший CSV

  1. UTF-8, если файл для Excel — с BOM.
  2. Заголовок в первой строке, имена без пробелов и повторов.
  3. Даты в формате ISO 8601: 2026-11-03 или 2026-11-03T14:05:00Z, без локальных форматов.
  4. Десятичный разделитель — точка, без разделителей тысяч.
  5. Пустое значение — пустое поле, а не NULL, - или н/д.
  6. Все поля с запятыми, кавычками и переводами строк — в кавычках, а лучше пусть это сделает библиотека.
  7. Если данные вложенные, подумайте о JSON Lines: по объекту JSON на строку, без проблем с экранированием.

Сравнить две выгрузки построчно удобно в инструменте сравнения текстов.