Перейти к содержанию

172 уроков, 13 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 18 из 20 Средний 40 мин 120 XP

CSV и JSON: две таблицы, два формата

У таблиц два языка: CSV для Excel и плоских выгрузок, JSON для типов и вложенности. Модуль csv переводит с обоих.

Редакция Питоники

JSON — язык API, но не единственный формат данных в жизни. Бухгалтерия присылает таблицы, Excel экспортирует выгрузки, отчёты уходят в файлах, которые открываются двойным щелчком. Всё это — CSV: самый простой табличный формат на свете. Половина данных мира живёт в нём до сих пор, и программист, который умеет читать и писать оба формата, не теряется ни в одном проекте. В этом уроке поставим оба формата рядом, научимся читать каждый и переводить данные с одного языка на другой.

Расшифровка уже в названии: comma-separated values, значения через запятую. Каждая строка файла — запись, внутри записи поля разделены запятой, первая строка обычно шапка с именами столбцов. Никакой магии — сейчас убедимся.

CSV — это просто текст

что лежит внутри csv-файла
import os

CSV_TEXT = """name,price
Клавиатура,4990
Мышь,1290
"""

with open("items.csv", "w", encoding="utf-8", newline="") as f:
    f.write(CSV_TEXT)

with open("items.csv", encoding="utf-8") as f:
    print(f.read())
os.remove("items.csv")
Вывод
name,price
Клавиатура,4990
Мышь,1290

Никаких скобок и кавычек — шапка, две строки данных, и всё. Именно эта простота сделала CSV универсальным: его понимают Excel, Google Таблицы, базы данных и любой язык программирования. Но за простоту платим: формат не знает ни про типы, ни про вложенность — он про текст и запятые, и точка.

csv.reader: строки-списки

Модуль csv входит в стандартную библиотеку и делает единственную полезную работу — аккуратно режет строки файла по полям. csv.reader превращает каждую строку в список значений.

reader отдаёт списки
import csv, os

CSV_TEXT = """name,price,rating
Клавиатура,4990,4.7
Мышь,1290,4.3
Наушники,7350,3.9
"""

with open("items.csv", "w", encoding="utf-8", newline="") as f:
    f.write(CSV_TEXT)

with open("items.csv", encoding="utf-8", newline="") as f:
    rows = list(csv.reader(f))

print(rows[0])
print(rows[1])
print(type(rows[1][1]).__name__, rows[1][1])
os.remove("items.csv")
Вывод
['name', 'price', 'rating']
['Клавиатура', '4990', '4.7']
str 4990

Смотри на третью строку вывода: цена приехала строкой, а не числом. CSV не хранит типы — «4990» для него просто текст между запятыми. Считать с ним среднее не выйдет, пока не сделаешь int() сам: это твоя работа при чтении CSV, и забывать её нельзя.

приводим типы и считаем
import csv, os

CSV_TEXT = """name,price,rating
Клавиатура,4990,4.7
Мышь,1290,4.3
Наушники,7350,3.9
"""

with open("items.csv", "w", encoding="utf-8", newline="") as f:
    f.write(CSV_TEXT)

with open("items.csv", encoding="utf-8", newline="") as f:
    reader = csv.reader(f)
    header = next(reader)
    prices = [int(row[1]) for row in reader]

print(header)
print("Средняя цена:", round(sum(prices) / len(prices)))
os.remove("items.csv")
Вывод
['name', 'price', 'rating']
Средняя цена: 4543

next(reader) прочитал шапку, а список включения прошёлся по оставшимся строкам, превратив цены в числа. Обрати внимание на index: цена — row[1], то есть второй столбец. Работает, но хрупко: передвинули столбцы в выгрузке — и все индексы поплыли.

csv.DictReader: строки-словари

DictReader решает проблему индексов: он сам читает шапку и делает из каждой строки словарь, где ключи — имена столбцов. Обращение по имени вместо позиции.

DictReader: доступ по имени столбца
import csv, os

CSV_TEXT = """name,price,rating
Клавиатура,4990,4.7
Мышь,1290,4.3
Наушники,7350,3.9
"""

with open("items.csv", "w", encoding="utf-8", newline="") as f:
    f.write(CSV_TEXT)

with open("items.csv", encoding="utf-8", newline="") as f:
    for row in csv.DictReader(f):
        print(row["name"], "-", row["price"], "- рейтинг", row["rating"])

os.remove("items.csv")
Вывод
Клавиатура - 4990 - рейтинг 4.7
Мышь - 1290 - рейтинг 4.3
Наушники - 7350 - рейтинг 3.9

Порядок столбцов теперь не важен — поле ищется по имени. Записи стали похожи на знакомые по курсу словари из ответа API, с одной разницей: значения здесь всё ещё строки, int() и float() никто не отменял.

фильтр из csv: сначала int, потом условие
import csv, os

CSV_TEXT = """name,price,rating
Клавиатура,4990,4.7
Мышь,1290,4.3
Наушники,7350,3.9
"""

with open("items.csv", "w", encoding="utf-8", newline="") as f:
    f.write(CSV_TEXT)

cheap = []
with open("items.csv", encoding="utf-8", newline="") as f:
    for row in csv.DictReader(f):
        if int(row["price"]) < 5000:
            cheap.append(row["name"])

print("Дешевле 5000:", ", ".join(cheap))
os.remove("items.csv")
Вывод
Дешевле 5000: Клавиатура, Мышь

Всё, что ты умел делать с данными из API, работает и над csv-таблицей — добавился только шаг int() на границе. Фильтр, среднее, топ-N: формат хранения не меняет приёмы обработки.

Точка с запятой: другой разделитель

Один нюанс, о котором узнаешь сразу после первого файла из Excel: в русской локали Excel сохраняет csv с разделителем «точка с запятой», потому что запятая занята десятичной дробью. Модуль csv умеет и такое — разделитель передаётся параметром delimiter.

csv с разделителем из Excel
import csv, os

CSV_SEMI = "name;price\nМышь;1290\nКоврик;890\n"

with open("items.csv", "w", encoding="utf-8", newline="") as f:
    f.write(CSV_SEMI)

with open("items.csv", encoding="utf-8", newline="") as f:
    rows = list(csv.reader(f, delimiter=";"))

print(rows[0])
print(rows[1][1])
os.remove("items.csv")
Вывод
['name', 'price']
1290

Запятая — соглашение по умолчанию, но формат скорее «значения через разделитель». Если файл открылся одной колонкой с мусором внутри — первым делом загляни в него блокнотом и проверь разделитель. И ещё: reader и DictReader — итераторы, они читают файл по строке, а не грузят его целиком; на выгрузке в миллион строк память скажет спасибо. Единственное исключение — list(reader): это осознанное «читаем всё», и берут его только когда данные нужны целиком.

Запятая внутри поля: кавычки

А что если в самом значении есть запятая — примечание «механика, подсветка» разорвётся на два поля? Не разорвётся: writer сам обернёт такое поле в кавычки, а reader сам их раскроет. Это ещё одна причина резать csv именно модулем, а не split-ом по запятой: модуль знает про кавычки, простой split — нет.

writer сам ставит кавычки
import csv, os

with open("quotes.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "note"])
    writer.writerow(["Клавиатура", "механика, подсветка"])

with open("quotes.csv", encoding="utf-8") as f:
    print(f.read())
os.remove("quotes.csv")
Вывод
name,note
Клавиатура,"механика, подсветка"

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

Когда CSV, а когда JSON

СвойствоCSVJSON
структураплоская таблица: строки и столбцылюбая вложенность: списки в словарях
типынет, всё текстчисла, bool, null сохраняются
кто открываетExcel, Google Таблицы, 1Спрограммы и API
вес и простотаминимум байт, формат на пальцахскобки и кавычки, но строгий
типичный источниквыгрузки, отчёты, прайсыответы API, конфиги

Правило выбора короткое. CSV хранит только текст и запятые, JSON — типы и вложенность: таблица едет в CSV, структура с глубиной — в JSON. Прайс из тысячи строк для бухгалтерии — CSV; ответ каталога с вложенными категориями и рейтингами — JSON. А когда получатель просит «просто эксельку» — вопросов вовсе не остаётся.

Конвертация: CSV в JSON

Конвертация звучит грозно, а выглядит как всё, что ты уже умеешь: прочитать один формат, привести типы, записать другой. Зачем она нужна — понятно из таблицы: данные приехали csv-файлом, а дальше поедут в API или в программу, которая говорит только JSON. DictReader отдаёт строки-словари — приведём поля к числам и отдадим json.dump.

csv превращается в json
import csv, json, os

CSV_TEXT = """name,price,rating
Клавиатура,4990,4.7
Мышь,1290,4.3
Наушники,7350,3.9
"""

with open("items.csv", "w", encoding="utf-8", newline="") as f:
    f.write(CSV_TEXT)

items = []
with open("items.csv", encoding="utf-8", newline="") as f:
    for row in csv.DictReader(f):
        items.append({
            "name": row["name"],
            "price": int(row["price"]),
            "rating": float(row["rating"]),
        })

with open("items.json", "w", encoding="utf-8") as f:
    json.dump(items, f, ensure_ascii=False, indent=2)

with open("items.json", encoding="utf-8") as f:
    back = json.load(f)

print(len(back), "товара в json")
print(back[0])
os.remove("items.csv")
os.remove("items.json")
Вывод
3 товара в json
{'name': 'Клавиатура', 'price': 4990, 'rating': 4.7}

В файле items.json теперь настоящие типы: цена — int, рейтинг — float. Именно их CSV потерял при экспорте, и именно их вернул твой код при конвертации — двадцать строк на стыке двух миров, без единой установки. Проверка обратным чтением из прошлого урока здесь как всегда: прочитали, убедились, что json корректен. Такой файл уже можно отдавать любой программе: типы на месте, кириллица читаемая, структура — список словарей.

Конвертация: JSON в CSV

Обратное направление встречается, когда данные из API надо отдать людям в Excel — бухгалтер просит «обычную табличку», и JSON её не устроит. csv.writer пишет строки по одной: сначала шапку, потом по списку значений на каждый товар. writerow принимает список значений любой длины — столбцов в csv может быть сколько нужно, лишь бы в каждой строке их было поровну.

json превращается в csv
import csv, json, os

API_RESPONSE = """
{
  "status": "ok",
  "source": "shop-api",
  "items": [
    {"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
    {"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
    {"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
    {"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
    {"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
  ]
}
"""

items = json.loads(API_RESPONSE)["items"]

with open("items.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "price", "rating"])
    for item in items:
        writer.writerow([item["name"], item["price"], item["rating"]])

with open("items.csv", encoding="utf-8", newline="") as f:
    rows = list(csv.reader(f))

print(len(rows), "строк вместе с шапкой")
print(rows[2])
os.remove("items.csv")
Вывод
6 строк вместе с шапкой
['Мышь', '1290', '4.3']

Шесть строк: шапка плюс пять товаров. В CSV уехали только три поля — флаг in_stock и служебные ключи ответа остались за бортом: для таблицы они не нужны. Выбрать, какие поля едут в CSV, — часть конвертации, и лучше решать её явно списком, как здесь: новый столбец — это элемент списка в writerow, а не правка половины скрипта. Числа writer превратит в текст сам — обратное преобразование остаётся на стороне читателя.

тот же экспорт силами pandas
# на своём компьютере, после pip install pandas
import pandas as pd

table = pd.read_csv("items.csv")             # таблица-DataFrame
table["price"] = table["price"].astype(int)  # типы вручную и тут
table.to_json("items.json", orient="records", force_ascii=False)
pandas не входит в стандартную библиотеку, в браузерной песочнице его нет — поэтому в курсе конвертация собирается из csv и json руками. Этот блок — для домашнего компьютера: там те же две операции занимают пару строк, а понимание, ЧТО они делают, у тебя уже есть.

Что дальше

Теперь у тебя оба табличных языка: reader и DictReader на приёме, writer на отправке, приведение типов на границе и ясное правило, какой формат для какой задачи. Осталось собрать всё изученное в один инструмент — следующий урок складывает фильтрацию, разбор ответа и сохранение в мини-клиент API на функциях.

Конвертация форматов — не волшебство, а три шага: прочитать, вернуть типы на место, записать по правилам получателя.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

import csv

row = next(csv.reader(["Мышь,1290,4.3"]))
print(row, len(row))
import csv

rows = list(csv.reader(["name,price", "Мышь,1290"]))
print(rows[1][0], int(rows[1][1]) + 1)
items = [{"name": "Мышь", "price": 1290}]

line = items[0]["name"] + "," + str(items[0]["price"])
print(line)
Проверь себя
0 / 6

1. Во что csv.reader превращает строку «Мышь,1290,4.3»?

2. Чем csv.DictReader удобнее csv.reader?

3. Что нужно сделать со значением row["price"] из CSV перед арифметикой?

4. Зачем csv-файлы открывают с newline=""?

5. Какой формат выбрать для прайса, который откроют в Excel?

6. Из каких шагов состоит конвертация CSV в JSON?

Карточки терминов
Запомнено: 0 / 6
Практика

Прочитай сохранённую CSV-выгрузку через csv.DictReader, приведи цену к числу и найди самый дорогой товар. Выведи строку вида «Наушники - 7350»: имя, дефис, цена.

practice.py
Вопросы и ответы по уроку

Как перевести CSV в JSON на Python?

Прочитать csv.DictReader-ом, привести значения к нужным типам (int, float) и записать json.dump с ensure_ascii=False и indent=2. CSV хранит только строки, поэтому приведение типов — обязательный шаг, иначе в JSON приедут текстовые цены.

Чем csv.DictReader отличается от csv.reader?

reader отдаёт каждую строку списком — доступ по позиции row[1]. DictReader берёт первую строку файла как шапку и отдаёт записи словарями: row["price"]. Для таблиц с понятной шапкой DictReader читаемее и не ломается от перестановки столбцов.

Почему в Python из csv-файла приходят строки вместо чисел?

Потому что в самом формате нет типов: CSV — это текст, где всё между запятыми. «4990» приходит строкой и превращается в число вручную: int(row["price"]). JSON, в отличие от CSV, типы хранит — это одна из главных причин выбирать его для данных, с которыми программа работает.

Когда данные лучше хранить в CSV, а когда в JSON?

Плоская таблица без вложенности, адресованная человеку или Excel, — CSV: просто, компактно, открывается везде. Вложенные структуры, булевы флаги, сохранение типов — JSON. Форматы свободно конвертируются друг в друга силами стандартной библиотеки csv и json.

Почему csv-файл из Excel открывается одной колонкой?

В русской локали Excel разделяет поля точкой с запятой, а не запятой: запятая занята десятичной дробью. Укажи разделитель при чтении: csv.reader(f, delimiter=";") или csv.DictReader(f, delimiter=";") — и колонки встанут на место. Первым делом всегда смотри на файл глазами: разделитель виден сразу.

Что сломается, если открыть csv в Excel и сохранить обратно?

Самое частое — кодировка и разделитель: старые Excel сохраняют в cp1251 и точкой с запятой, а дробные числа могут превратиться в даты. Правило гигиены: csv-файлы для программ правят скриптом, а не руками в Excel; Excel для них — зритель, а не редактор. Если без правки руками не обойтись, при сохранении выбирай явно utf-8 и проверяй результат чтением через DictReader.

Как csv.reader работает с большими файлами?

reader и DictReader — итераторы: строки читаются по одной в цикле, весь файл в память не грузится. Поэтому цикл for row in reader спокойно переваривает выгрузку в миллионы строк. Список целиком нужен только когда данные обязаны лежать в памяти: rows = list(reader).

Понравился урок? Сошлитесь на него

«CSV хранит только текст и запятые, JSON — типы и вложенность: таблица едет в CSV, структура с глубиной — в JSON.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.