🔥Как быстро и эффективно работать с большими JSON-файлами

Узнайте три подхода к обработке больших объемов данных в формате JSON, включая даже альтернативы традиционным базам данных.

933 открытий8К показов
🔥Как быстро и эффективно работать с большими JSON-файлами

Разработчики используют API каждый день, и подавляющее их число отдает данные в виде JSON-массивов, будь то логи бота или резюме кандидатов с площадок по поиску работы. С небольшими файлами.json учат обращаться на многих курсах программирования, но что делать, если объем такого вывода становится некомфортно большим? Или вы регулярно «упираетесь» в ошибки, вызванные разнородной структурой элементов? В этой статье мы познакомим вас с тремя решениями, которые помогут эффективно работать с большими JSON файлами.

Если вы только-только начали изучать способы хранения, знания JSON можно освежить здесь.

Способ первый: параллельная обработка

Классическое решение, задействующее навыки параллелизации. К примеру, если признак name каждого элемента требует обновления:

			json_data = [
    {"id": 1, "name": "Item 1"},
    {"id": 2, "name": "Item 2"},    …
    {"id": 3, "name": "Item 3"}
]
		

Мы можем задать две функции. Первая из них добавляет значению name префикс Test:

			async def update_json_array(json_data, api_base_url):
    async with aiohttp.ClientSession() as session:
        tasks = []
        for element in json_data:
            # Обновляем значение name, добавляя "Test" в начало
            element['name'] = "Test " + element['name']
            
            # Здесь формируем URL без ID, так как мы отправляем POST-запрос
            tasks.append(update_element(session, api_base_url, element))
        
        # Ждем завершения всех задач
        results = await asyncio.gather(*tasks)
        return results
		

А вторая пушит обновление:

			async def update_element(session, api_base_url, element):
    # Отправляем POST запрос на создание нового ресурса
    async with session.post(api_base_url, json=element) as response:
        # Получаем и возвращаем ответ в виде JSON
        return await response.json()
		

В итоге мы распараллеливаем запуск этих функций с помощью asyncio:

			import asyncio
import json

api_base_url = "http://example.com/api/items"

# Асинхронный цикл
loop = asyncio.get_event_loop()
updated_results =  loop.run_until_complete(update_json_array(json_data, api_base_url))
		

Способ второй: пакетная обработка в binary

В комьюнити Hadoop и Spark (для хранения больших данных) особое признание обрел формат Parquet. Когда речь идет об огромных объемах информации, удобство ее обработки превалирует над читаемостью. Здесь вообще рекомендую избегать подключения pandas и перевода в человекочитаемый формат в промежутке.

Такой код:

			table = pa.Table.from_pandas(df)
		
  1. «Заморозит» вашу программу, если файл слишком большой;
  2. Не учитывает массивы с меняющейся структурой.

А до этого RAM вообще может закончится на шаге конвертации JSON в датафрейм.

В такой ситуации поможет библиотека ijson:

			import pyarrow as pa
import pyarrow.parquet as pq
import ijson

input_file = 'large_file.json'
output_file = 'output.parquet'

# Список для хранения данных
rows = []

# Открываем JSON-файл и читаем его построчно
with open(input_file, 'r') as f:
    # Итерируем поэлементно
    for obj in ijson.items(f, 'item'):
        rows.append(obj)

        # Банчи по 1000 записей сохраняем в Parquet
        if len(rows) >= 1000:
            table = pa.Table.from_pylist(rows)
            # В режиме добавления
            pq.write_table(table, output_file, append=True)
            rows = []
		

К примеру, конверсия кортежа в binary:

			data = (7, 3.14, 10)
		

Превратит числа вот в такую компактную и быстродейственную абракадабру:

			b'\x07\x00\x00\x00\xc3\xf5H@\n\x00'
		

Способ третий: перейти в другой формат

На курсах повышения квалификации нашу группу познакомили с Redis — альтернативой классическим базам вроде PostgreSQL. Так здорово осознавать, что до тебя немало людей уже отстрадались на ниве JSON и даже создали целое решение, «бьющее» самые распространенные проблемы — разнородность элементов массива, вложенные узлы.

Представьте, сколько энергии потребуется даже с ChatGPT, чтобы написать скрипт на Python, который «схлопнет» до табличного состояния данные кандидатов ниже?

			[
    {
        "id": 1,
        "name": "Иван Иванов",
        "experience": {
            "years": 5,
            "projects": [
                {
                    "name": "Интернет-магазин",
                    "role": "Ведущий разработчик",
                    "technologies": ["PHP", "MySQL", "JavaScript"],
                    "description": "Разработка и поддержка интернет-магазина с высокой нагрузкой."
                },
                {
                    "name": "Корпоративный портал",
                    "role": "PHP-разработчик",
                    "technologies": ["PHP", "Laravel", "HTML", "CSS"],
                    "description": "Создание корпоративного портала для внутреннего использования."
                }
            ]
        },
        "skills": ["PHP", "MySQL", "Git", "HTML", "CSS"],
        "education": {
            "degree": "Бакалавр",
            "institution": "Национальный университет",
            "year": 2018
        },
        "location": "Москва",
        "contact": {
            "email": "ivan.ivanov@example.com",
            "phone": "+7 (999) 123-45-67"
        }
    },
    {
        "id": 2,
        "name": "Мария Петрова",
        "experience": {
            "years": 3,
            "projects": [
                {
                    "name": "Система управления задачами",
                    "role": "PHP-разработчик",
                    "technologies": ["PHP", "PostgreSQL", "Vue.js"],
                    "description": "Разработка веб-приложения для управления задачами внутри команды."
                }
            ]
        },
        "skills": ["PHP", "PostgreSQL", "JavaScript", "Git"],
        "education": {
            "degree": "Магистр",
            "institution": "Технический университет",
            "year": 2020
        },
        "location": "Санкт-Петербург",
        "contact": {
            "email": "maria.petrova@example.com",
            "phone": "+7 (999) 234-56-78"
        }
    }]
		

Пару лет назад я занималась подобным перед загрузкой логов бота в BigQuery (SQL-подобная база), а потом была вынуждена обрабатывать ситуацию «забытых» полей (они проявлялись реже, чем раз в неделю, на которой опробовали скрипт выгрузки). Это приводило к необходимости обновлять схему таблицы, заниматься перезаливом и в целом фрустрироваться ситуацией.

Теперь понимаю: лучший способ сократить мороку при обращении с массивами — отойти от формата строго заданной структуры как можно раньше. Redis буквально создан для этого. В подгружаемом массиве через месяц появился экземпляр кандидата с новым полем portfolio? «Редиска» положит к себе и такое, причем без множественных ошибок. Захотите в дальнейшем использовать данные таблично? Вычитайте сет с помощью самописной функции:

			import redis

r = redis.Redis(
    host = ,
    port = ,
    password = ,
)

# Функция преобразования данных в датафрейм
def read_redis_set_to_dataframe(redis_set_name):
    # Чтение всех элементов сета
    redis_set = r.smembers(redis_set_name)

    data = [json.loads(item) for item in redis_set] # Перевод строковых представлений словарей в объекты Python
    df = pd.DataFrame(data)

    return df

redis_set_name = 'candidates'

df = read_redis_set_to_dataframe(redis_set_name)
		
🔥Как быстро и эффективно работать с большими JSON-файлами 1

И что немаловажно, данные хранятся в оперативной памяти сервера, что ускоряет обращение с ними, даже в случае с большими порциями. Если ваша компания, конечно, не испытывает проблем с масштабируемостью.

Приятный бонус: логика сета (это аналог таблицы в базе) подразумевает уникальные значения. То есть очистка от повторений будет произведена автоматически. И тут ощущаются спасенные человекочасы.

🔥Как быстро и эффективно работать с большими JSON-файлами 2

Многие провайдеры облачных серверов предлагают преднастроенный Redis, который за 5-10 минут встанет из-под Docker-контейнера, и цены на такие услуги стремятся к тем же минимумам, что и голый Ubuntu на миникалках (300 рублей в месяц против 130).

Среди недостатков «редиски» отмечу, что переход от таблиц к сетам может вызвать у разработчика с информационной перегрузкой дополнительный стресс: документация весьма непростая и перестроиться на нетабличное восприятие поначалу потребует много энергии. Но тут очень здорово помогает ChatGPT.

Заключение

Если вы дорасли до проектов с массивными объемами данных, это уже прекрасно. Порой стоит позволить себе наошибаться при обращении с ними, пока не подберете наилучшее для ситуации решение. ijson немного сложнее поддерживать, Redis плохо подходит новичкам, asyncio тоже не идеален. В каждом проекте свои тонкости — они и определят, какое из решений оптимальное.

Следите за новыми постами
Следите за новыми постами по любимым темам
933 открытий8К показов