Тема 14: Сохранение данных: CSV, JSON или База Данных? Как писать на диск и не тормозить асинхронный парсер?
Ты написал парсер-ракету. Он качает 1000 страниц в секунду. И тут ты пишешь:
Поздравляю, ты убил асинхронность.
Обычная запись в файл (open) — это блокирующая операция. Пока жесткий диск (даже SSD) крутит блинами или пишет ячейки, твой скрипт стоит. Весь Event Loop встает колом.
Твоя задача: сбрасывать данные так, чтобы парсер этого даже не замечал.
1. Битва Форматов
JSON (Классический) — "Ловушка памяти"
[ {item1}, {item2}, ... ]
- Проблема: Чтобы записать валидный JSON, тебе нужно собрать ВСЕ данные в памяти, и только в конце сделать
json.dump(). - Результат: Если ты парсишь миллион товаров, у тебя кончится оперативка (OOM Kill), и скрипт умрет, не сохранив ни байта.
JSON Lines (.jsonl) — "Выбор Профессионала"
{item1}{item2}
- Суть: Каждая строка — отдельный валидный JSON-объект.
- Плюсы:
- Append-only: Можно дописывать по одной строке в конец файла бесконечно.
- Надежность: Скрипт упал на середине? Всё, что записано — цело.
- Потоковость: Легко читать по одной строке, не загружая файл в память целиком.
CSV — "Для менеджеров"
- Плюсы: Открывается в Excel.
- Минусы: Ад с вложенностью. Как записать список характеристик в одну ячейку? Через запятую? Кавычки? Это боль. Используй только для плоских таблиц.
Базы Данных (SQLite / PostgreSQL)
- SQLite: Если данных < 5 млн строк и ты работаешь один. Файл базы лежит рядом. Быстро, надежно (ACID).
- PostgreSQL: Если данных много, или парсер работает на нескольких серверах одновременно.
2. Техника: Буферизация (Batching)
Не пиши на диск каждый товар. Это изнасилует диск (IOPS). Используй буфер.
- Копишь товары в памяти (список).
- Набралось 100 штук (или 1000)?
- Сбрасываешь пачкой (Flush) на диск или в БД.
- Очищаешь память.
3. Инструменты: Асинхронная запись
Чтобы не блокировать цикл во время записи, нужны специальные либы:
- Файлы:
aiofiles - SQLite:
aiosqlite - Postgres:
asyncpg(самый быстрый драйвер в мире Python)
👨💻 КОД: Умный Сохранятор (JSONL + Buffer)
Напишем класс, который копит данные и асинхронно сбрасывает их в data.jsonl.
import asyncio
import aiofiles
import json
from typing import List, Dict
class AsyncJsonlSaver:
def __init__(self, filename: str, batch_size: int = 100):
self.filename = filename
self.batch_size = batch_size
self.buffer: List[Dict] = []
async def add(self, item: Dict):
"""Добавляем товар в буфер. Если полон - сбрасываем."""
self.buffer.append(item)
if len(self.buffer) >= self.batch_size:
await self.flush()
async def flush(self):
"""Пишем буфер на диск асинхронно"""
if not self.buffer:
return
# Открываем файл в режиме 'append' (дозапись)
async with aiofiles.open(self.filename, mode='a', encoding='utf-8') as f:
# Формируем большой кусок текста
lines = [json.dumps(item, ensure_ascii=False) for item in self.buffer]
blob = "\\n".join(lines) + "\\n"
# Пишем один раз большой кусок
await f.write(blob)
print(f"💾 Сбросил {len(self.buffer)} записей на диск.")
self.buffer.clear() # Чистим буфер
# --- ИСПОЛЬЗОВАНИЕ В ПАРСЕРЕ ---
async def main():
saver = AsyncJsonlSaver("products.jsonl", batch_size=50)
# Имитация парсинга
for i in range(105):
item = {"id": i, "name": f"Товар {i}", "price": 100 + i}
# Это происходит мгновенно, запись будет только на 50 и 100 элементе
await saver.add(item)
# ВАЖНО: В конце всегда нужно сбросить остатки (последние 5 товаров)
await saver.flush()
if __name__ == "__main__":
asyncio.run(main())
🧠 Финальное напутствие Архитектора
Поздравляю. Ты прошел путь от "Как спарсить страничку" до "Как построить промышленный харвестер данных".
У тебя есть все карты:
- Разведка: Ты знаешь про SSR/CSR и скрытые JSON-ы.
- Инструменты: Ты выкинул
requestsи взялhttpx. Ты знаешь проPlaywrightиDrissionPage. - Взлом: Ты умеешь искать API и подделывать TLS-отпечатки.
- Код: Твой парсер асинхронный (Producer/Consumer), типизированный (Pydantic) и пишет данные батчами.
Что делать дальше? Иди и спарси что-нибудь сложное. Теория без практики мертва. Возьми сайт, который тебя бесит, и вытащи из него данные. Наткнешься на бан — вспомни про прокси и TLS. Упрешься в скорость — вспомни про профайлинг и Selectolax.