CSV

Теория

CSV — простой табличный формат: одна строка — одна запись, значения разделены запятой (или точкой с запятой). Его открывает Excel, принимают бухгалтерские программы, в нём поставщики присылают прайсы, а маркетплейсы — заказы. Поэтому выгрузки и импорт в backend часто — это CSV.

Выглядит просто, и возникает соблазн разбирать CSV через line.split(","). Так делать нельзя: значение может содержать запятую или перевод строки — тогда его берут в кавычки ("Мышь, беспроводная"), и split разрежет его пополам. Модуль csv знает все эти правила. csv.DictReader читает строки как словари по заголовку, csv.DictWriter пишет словари. Важно помнить: все значения из CSV — строки, числа и цены нужно приводить самому. Файлы для модуля csv открывают с newline="" — модуль сам управляет переводами строк, иначе на Windows появятся пустые строки между записями. Русский Excel по умолчанию сохраняет CSV с разделителем ; — его указывают параметром delimiter.

Наглядно

Таблица 1. Инструменты модуля csv.

Инструмент Что делает Строка выглядит как
csv.reader(f) читает строки как списки ['42', 'Мышь', '1499.90']
csv.DictReader(f) читает строки как словари по заголовку {'id': '42', 'name': 'Мышь', 'price': '1499.90'}
csv.writer(f).writerow(list) пишет список как строку
csv.DictWriter(f, fieldnames=[...]) пишет словари; writeheader(), writerows(rows)
delimiter=";" другой разделитель для CSV из русского Excel
open(..., newline="", encoding="utf-8") так открывают файлы для csv

Таблица 2. Почему не split(",").

Строка файла split(",") csv
42,Мышь,1499.90 3 значения 3 значения
43,"Мышь, беспроводная",1990.00 4 значения — название разрезано 3 значения
44,"Коврик ""Pro""",490.00 кавычки остались в тексте Коврик "Pro"

На практике

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

Шаг 1. Видим, почему split не подходит

Прайс поставщика, в одном названии есть запятая. Файл price.csv:

csv
id,name,price
42,Мышь,1499.90
43,"Мышь, беспроводная",1990.00

Сравним наивный разбор и модуль csv:

python
import csv

with open("price.csv", encoding="utf-8") as f:
    lines = f.read().splitlines()
print(lines[2].split(","))

with open("price.csv", newline="", encoding="utf-8") as f:
    rows = list(csv.reader(f))
print(rows[2])

Результат:

вывод
['43', '"Мышь', ' беспроводная"', '1990.00']
['43', 'Мышь, беспроводная', '1990.00']

Шаг 2. Читаем строки как словари

DictReader берёт заголовок из первой строки и отдаёт каждую запись словарём — к полям обращаемся по имени:

python
with open("price.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        print(row["id"], row["name"], row["price"], type(row["price"]))

Результат:

вывод
42 Мышь 1499.90 <class 'str'>
43 Мышь, беспроводная 1990.00 <class 'str'>

Цена — строка. Для расчётов её приводят: Decimal(row["price"]).

Шаг 3. Считаем выручку

Выгрузка заказов из маркетплейса. Файл orders.csv:

csv
order_id,status,total
1042,paid,1499.90
1043,cancelled,5990.00
1044,paid,490.00

Суммируем оплаченные заказы, приводя строки к Decimal:

python
from decimal import Decimal

with open("orders.csv", newline="", encoding="utf-8") as f:
    revenue = sum(Decimal(row["total"]) for row in csv.DictReader(f) if row["status"] == "paid")
print(revenue)

Результат:

вывод
1989.90

Файл читается построчно — так же можно обработать выгрузку на миллион строк.

Шаг 4. Записываем отчёт

DictWriter пишет словари; заголовок — writeheader(). Файл открываем с newline="":

python
report = [
    {"status": "paid", "count": 2, "revenue": "1989.90"},
    {"status": "cancelled", "count": 1, "revenue": "0"},
]
with open("report.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["status", "count", "revenue"])
    writer.writeheader()
    writer.writerows(report)

with open("report.csv", encoding="utf-8") as f:
    print(f.read(), end="")

Результат:

вывод
status,count,revenue
paid,2,1989.90
cancelled,1,0

Шаг 5. Читаем CSV из русского Excel

Excel с русскими настройками сохраняет CSV с точкой с запятой. Файл excel.csv:

csv
id;name;price
42;Мышь;1499,90

С разделителем по умолчанию вся строка станет одним полем — указываем delimiter=";":

python
with open("excel.csv", newline="", encoding="utf-8") as f:
    print(next(csv.DictReader(f)))
with open("excel.csv", newline="", encoding="utf-8") as f:
    row = next(csv.DictReader(f, delimiter=";"))
print(row, Decimal(row["price"].replace(",", ".")))

Результат:

вывод
{'id;name;price': '42;Мышь;1499', None: ['90']}
{'id': '42', 'name': 'Мышь', 'price': '1499,90'} 1499.90

В таком файле и дробная часть отделена запятой — перед Decimal её меняют на точку.

Частые ошибки

  • split(",") вместо модуля csv. Запятые и кавычки внутри значений разрезают поля.
  • Значения как числа. Всё из CSV — строки: sum(row["total"] ...) падает с TypeError. Приводите явно: int(...), Decimal(...).
  • Не тот разделитель. CSV из русского Excel — с ;; с разделителем по умолчанию вся строка попадёт в одно поле.
  • Без newline="". На Windows между записями появляются пустые строки, а переводы строк внутри значений ломаются.
  • float для денег. Как и везде, цены из CSV — в Decimal.

Итог

Чтение

  • csv.reader(f) — строки списками, csv.DictReader(f) — словарями по заголовку.
  • Файл: open(путь, newline="", encoding="utf-8"); читается построчно — подходит для больших выгрузок.
  • Все значения — строки; числа и цены приводят сами.

Запись

  • csv.DictWriter(f, fieldnames=[...]), writeheader(), writerows(rows).

Форматы

  • Значения с запятой или кавычками — модуль csv экранирует и разбирает их сам.
  • delimiter=";" — для CSV из русского Excel; там же дробная часть может быть с запятой.

Вопросы на собеседовании

Почему CSV нельзя разбирать через split(",")?

Значения могут содержать разделитель, кавычки и переводы строк — тогда они берутся в кавычки, а кавычки внутри удваиваются. split этих правил не знает и режет значения. Модуль csv реализует формат правильно.

Зачем открывать файл с newline="" для модуля csv?

Модуль csv сам обрабатывает переводы строк, в том числе внутри значений в кавычках. Без newline="" Python преобразует переводы строк сам, и на Windows при записи появляются лишние пустые строки, а при чтении ломаются многострочные значения.

Как отдать пользователю CSV на миллион строк?

Не собирая его целиком в памяти: писать построчно генератором и отдавать потоком. В Django для этого есть StreamingHttpResponse с генератором строк, сформированных через csv.writer.

Ошибка в тексте

Сообщить об ошибке могут зарегистрированные пользователи — так в сообщениях меньше спама.

Войти, чтобы сообщить