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

Тема 14: Сохранение данных: CSV, JSON или База Данных? Как писать на диск и не тормозить асинхронный парсер?

Ты написал парсер-ракету. Он качает 1000 страниц в секунду. И тут ты пишешь:

with open('data.txt', 'a') as f:  # <--- ТОРМОЗНОЙ ПАРАШЮТ
    f.write(data)

Поздравляю, ты убил асинхронность. Обычная запись в файл (open) — это блокирующая операция. Пока жесткий диск (даже SSD) крутит блинами или пишет ячейки, твой скрипт стоит. Весь Event Loop встает колом.

Твоя задача: сбрасывать данные так, чтобы парсер этого даже не замечал.


1. Битва Форматов

JSON (Классический) — "Ловушка памяти"

[ {item1}, {item2}, ... ]

  • Проблема: Чтобы записать валидный JSON, тебе нужно собрать ВСЕ данные в памяти, и только в конце сделать json.dump().
  • Результат: Если ты парсишь миллион товаров, у тебя кончится оперативка (OOM Kill), и скрипт умрет, не сохранив ни байта.

JSON Lines (.jsonl) — "Выбор Профессионала"

{item1}{item2}

  • Суть: Каждая строка — отдельный валидный JSON-объект.
  • Плюсы:
    1. Append-only: Можно дописывать по одной строке в конец файла бесконечно.
    2. Надежность: Скрипт упал на середине? Всё, что записано — цело.
    3. Потоковость: Легко читать по одной строке, не загружая файл в память целиком.

CSV — "Для менеджеров"

  • Плюсы: Открывается в Excel.
  • Минусы: Ад с вложенностью. Как записать список характеристик в одну ячейку? Через запятую? Кавычки? Это боль. Используй только для плоских таблиц.

Базы Данных (SQLite / PostgreSQL)

  • SQLite: Если данных < 5 млн строк и ты работаешь один. Файл базы лежит рядом. Быстро, надежно (ACID).
  • PostgreSQL: Если данных много, или парсер работает на нескольких серверах одновременно.

2. Техника: Буферизация (Batching)

Не пиши на диск каждый товар. Это изнасилует диск (IOPS). Используй буфер.

  1. Копишь товары в памяти (список).
  2. Набралось 100 штук (или 1000)?
  3. Сбрасываешь пачкой (Flush) на диск или в БД.
  4. Очищаешь память.

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())

🧠 Финальное напутствие Архитектора

Поздравляю. Ты прошел путь от "Как спарсить страничку" до "Как построить промышленный харвестер данных".

У тебя есть все карты:

  1. Разведка: Ты знаешь про SSR/CSR и скрытые JSON-ы.
  2. Инструменты: Ты выкинул requests и взял httpx. Ты знаешь про Playwright и DrissionPage.
  3. Взлом: Ты умеешь искать API и подделывать TLS-отпечатки.
  4. Код: Твой парсер асинхронный (Producer/Consumer), типизированный (Pydantic) и пишет данные батчами.

Что делать дальше? Иди и спарси что-нибудь сложное. Теория без практики мертва. Возьми сайт, который тебя бесит, и вытащи из него данные. Наткнешься на бан — вспомни про прокси и TLS. Упрешься в скорость — вспомни про профайлинг и Selectolax.