Преобразование ответа HTTP в словарь JSON

Раздел: Веб-разработка -> HTTP и JSON

Основные методы получения JSON из HTTP ответа

Как получить JSON из ответа HTTP с помощью библиотеки requests?

Библиотека requests является наиболее популярным инструментом для HTTP запросов в Python. Метод .json() объекта Response автоматически декодирует тело ответа в Python словарь или список, если Content-Type содержит application/json. Этот способ считается самым простым и надежным.

import requests

response = requests.get('https://api.example.com/data')
if response.status_code == 200:
    data = response.json()
    print(data)
else:
    print('Ошибка:', response.status_code)

Response json python (получение json из ответа http в python)

Пояснение: вызов .json() парсит JSON и возвращает структуру данных Python. В случае невалидного JSON возникает исключение json.JSONDecodeError. Для обработки ошибки рекомендуется оборачивать вызов в try/except.

Типичная проблема: сервер возвращает ответ с кодом 200, но тело содержит не JSON, а HTML или иную разметку. В таком случае response.json() выбросит ошибку. Решение: проверять Content-Type или обрабатывать ошибку и пытаться прочитать текст как резервный вариант.

try:
    data = response.json()
except ValueError:
    data = response.text
    print('Получен не JSON:', data[:200])

Как получить JSON, используя стандартную библиотеку urllib?

Для случаев, когда нельзя устанавливать сторонние пакеты, подходит модуль urllib.request. Ответ необходимо прочитать и декодировать с помощью json.loads().

import urllib.request
import json

url = 'https://api.example.com/data'
req = urllib.request.Request(url)
with urllib.request.urlopen(req) as resp:
    body = resp.read().decode('utf-8')
    data = json.loads(body)
    print(data)

Пояснение: urlopen возвращает файловый объект, из которого читаются байты. Их декодируют в строку (обычно UTF-8) и затем преобразуют в Python объект с помощью json.loads().

Проблема: если ответ содержит BOM (Byte Order Mark) или иную кодировку, нужно явно указать кодировку. Решение: использовать chardet для определения кодировки, либо читать заголовок Content-Type.

raw = resp.read()
encoding = resp.headers.get_content_charset() or 'utf-8'
text = raw.decode(encoding)
data = json.loads(text)

Как асинхронно получить JSON с помощью aiohttp?

Для асинхронных приложений (asyncio) используется библиотека aiohttp. Запрос выполняется в корутине с использованием async/await, ответ также парсится через встроенный метод .json().

import aiohttp
import asyncio

async def fetch_json(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.json()

result = asyncio.run(fetch_json('https://api.example.com/data'))
print(result)

Пояснение: aiohttp поддерживает пул соединений, автоматическое следование редиректам и управление куками. Метод .json() внутри выполняет декодирование байтового потока так же, как и в requests.

Проблема: при большом числе запросов может возникнуть перегрузка event loop. Решение: использовать семафоры или ограничивать количество одновременных соединений через ClientSession с параметром connector.

connector = aiohttp.TCPConnector(limit=10)
async with aiohttp.ClientSession(connector=connector) as session:
    tasks = [fetch_json(url) for url in urls]
    results = await asyncio.gather(*tasks)

Как обработать ошибки при разборе JSON из HTTP ответа?

Независимо от библиотеки, JSON может быть поврежден или содержать некорректные данные. Нужно перехватывать исключения json.decoder.JSONDecodeError (или ValueError в старых версиях).

import requests

try:
    resp = requests.get('https://api.example.com/invalid')
    data = resp.json()
except requests.exceptions.RequestException as e:
    print('Ошибка HTTP:', e)
except ValueError as e:
    print('Ошибка парсинга JSON:', e)
    print('Содержимое ответа:', resp.text[:500])

Пояснение: сначала ловится сетевая ошибка, затем ошибка парсинга. Если JSON в ответе частично поврежден, можно попробовать восстановить его, но это редко рекомендуется.

Частая ошибка: игнорируется код состояния HTTP. Даже при успешном статусе (2xx) тело может быть пустым. Решение: проверять response.ok или явно статус.

if resp.ok and resp.text:
    data = resp.json()
else:
    print('Ответ не содержит данных')

Как получить JSON из ответа с авторизацией (токен/базовое аутентификация)?

Некоторые API требуют передачу токена в заголовке или базовую аутентификацию. В библиотеке requests это делается параметром headers или auth.

import requests

url = 'https://api.example.com/protected'
headers = {'Authorization': 'Bearer YOUR_TOKEN'}
response = requests.get(url, headers=headers)
if response.status_code == 200:
    data = response.json()
    print(data)

Для базовой аутентификации используйте кортеж из логина и пароля: requests.get(url, auth=('user', 'pass')). Библиотека сама закодирует заголовок.

Проблема: если токен истек или неверен, API вернет 401. Решение: обрабатывать статус 401 и обновлять токен по необходимости.

Расширенные примеры работы с JSON из HTTP ответов

1. Потоковая загрузка большого JSON с частичной обработкой

Если JSON очень большой, может потребоваться обрабатывать его по частям, не загружая целиком в память. Используется параметр stream=True в requests и итерация по чанкам с помощью iter_content(), но для JSON это сложно, так как JSON - цельный формат. Альтернатива: использовать ijson для потокового парсинга.

Пример
import requests
import ijson

response = requests.get('https://api.example.com/large-data', stream=True)
parser = ijson.parse(response.raw)
for prefix, event, value in parser:
    if event == 'map_key' and value == 'key_of_interest':
        print('Найден ключ:', value)

Пояснение: response.raw предоставляет файловый объект, который передается ijson. Это позволяет обрабатывать JSON, не загружая его целиком.

(вывод зависит от данных)

2. Сохранение JSON ответа в файл и последующее чтение

Иногда ответ нужно сохранить для повторного использования или кэширования.

Пример
import requests
import json

resp = requests.get('https://api.example.com/data')
if resp.ok:
    # Сохраняем как есть
    with open('response.json', 'w', encoding='utf-8') as f:
        json.dump(resp.json(), f, ensure_ascii=False, indent=2)
    # Читаем обратно
    with open('response.json', 'r', encoding='utf-8') as f:
        data = json.load(f)
    print(data['key'])

Пояснение: json.dump записывает Python объект в файл. Параметр ensure_ascii=False сохраняет символы Юникода без экранирования.

3. Параллельные запросы с обработкой JSON (concurrent.futures)

Для ускорения сбора данных с нескольких URL используется пул потоков или процессов.

Пример
import requests
from concurrent.futures import ThreadPoolExecutor

def fetch_single(url):
    resp = requests.get(url)
    return resp.json() if resp.ok else None

urls = ['https://api1.example.com', 'https://api2.example.com']
with ThreadPoolExecutor(max_workers=5) as executor:
    results = list(executor.map(fetch_single, urls))
print(results)

Результат: списки словарей для каждого URL.

[{'key': 'value1'}, {'key': 'value2'}]

4. Использование httpx для синхронных и асинхронных запросов

Библиотека httpx поддерживает как синхронный, так и асинхронный интерфейс, а также HTTP/2.

Пример
import httpx

# Синхронно
with httpx.Client() as client:
    response = client.get('https://api.example.com/data')
    data = response.json()
    print(data)

# Асинхронно
import asyncio
async def async_fetch():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://api.example.com/data')
        return response.json()

result = asyncio.run(async_fetch())
print(result)

Пояснение: httpx имеет схожий API с requests, но поддерживает больше возможностей, такие как настраиваемые транспорты и таймауты.

5. Обработка вложенных или сложных структур JSON

Часто JSON содержит вложенные объекты или массивы. Допустим, ответ API содержит список пользователей с адресами.

Пример
import requests

resp = requests.get('https://api.example.com/users')
users = resp.json()
# Извлекаем все email
emails = [user['email'] for user in users]
# Группируем по городу
from collections import defaultdict
by_city = defaultdict(list)
for user in users:
    city = user['address']['city']
    by_city[city].append(user['name'])
print(dict(by_city))

Пояснение: типичная работа со списками словарей. Важно проверять наличие ключей, чтобы избежать KeyError.

{
  'New York': ['Alice', 'Bob'],
  'London': ['Charlie']
}

6. Автоматическая обработка пагинации с JSON ответами

Многие API используют пагинацию (например, по страницам). В ответе может быть поле next или аналогичное.

Пример
import requests

def fetch_all_pages(base_url, params=None):
    all_data = []
    url = base_url
    while url:
        resp = requests.get(url, params=params)
        data = resp.json()
        all_data.extend(data.get('results', []))
        url = data.get('next')  # url следующей страницы
    return all_data

result = fetch_all_pages('https://api.example.com/items')
print(len(result))

Пояснение: цикл продолжается, пока есть next URL. Нужно учитывать, что API может возвращать next как полный URL или только путь.

7. Работа с JSONP (JSON with Padding)

Редкий случай: сервер возвращает JSONP (функция, оборачивающая JSON). Чтобы получить чистый JSON, нужно извлечь его из строки.

Пример
import re
import json

resp_text = 'callback({"key":"value"})'
match = re.search(r'\(\{.*\}\)', resp_text)
if match:
    json_str = match.group()[1:-1]  # убираем скобки
    data = json.loads(json_str)
    print(data)

Пояснение: обычно используется регулярное выражение для выделения объекта JSON внутри скобок.

8. Использование сессий с сохранением cookies и заголовков

Сессия в requests сохраняет cookies между запросами, что полезно для авторизованных API.

Пример
session = requests.Session()
session.headers.update({'User-Agent': 'MyApp/1.0'})
session.auth = ('user', 'pass')

resp1 = session.get('https://api.example.com/login')
# cookies сохраняются автоматически
resp2 = session.get('https://api.example.com/secret')
data = resp2.json()
print(data)

9. Обработка ответа с неправильной кодировкой

Иногда сервер не указывает кодировку или указывает неверно. Можно принудительно декодировать в нужной кодировке перед парсингом JSON.

Пример
import requests

resp = requests.get('https://api.example.com/data')
raw = resp.content  # байты
try:
    # попытка декодировать как UTF-8
    text = raw.decode('utf-8')
except UnicodeDecodeError:
    # альтернативная кодировка
    text = raw.decode('cp1251')
data = json.loads(text)
print(data)

10. Таймауты и повторные попытки (retry) при получении JSON

Для надежности устанавливают таймаут и автоматически повторяют запрос при временных ошибках.

Пример
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

session = requests.Session()
retry_strategy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount('http://', adapter)
session.mount('https://', adapter)

try:
    resp = session.get('https://api.example.com/data', timeout=5)
    data = resp.json()
except requests.exceptions.RequestException as e:
    print('Не удалось получить данные:', e)

Пояснение: настройка retry автоматически повторяет запрос при указанных ошибках сервера.

Получение JSON из ответа HTTP в Python - comments

En
Response json python (python)