Отправка JSON POST-запроса с Python requests
requests.post(url, json=payload) отправляет JSON-тело на сервер в одну строку. Аргумент json= сериализует словарь, устанавливает заголовок Content-Type: application/json и формирует тело запроса. Ответ содержит статус-код и, в большинстве API, данные, которые сохранил сервер, что позволяет убедиться, что вызов выполнил задуманное. Это паттерн для записи в REST API, вебхуков и любых эндпоинтов, ожидающих JSON-нагрузку.
Пример Python requests.post Для JSON
Вывод:
Результат появится здесь...
Вывод:
Status: 201
Server stored: {'language': 'Python', 'stars': 3, 'id': 101}
Как Работает Этот Пример
json=сериализует словарь и устанавливаетContent-Type: application/json, поэтому сервер получает JSON-объект, а не поля формы.timeout=10ограничивает ожидание, поэтому недоступный эндпоинт быстро завершается с ошибкой, а не зависает.raise_for_status()преобразует ответ 4xx или 5xx в исключение, чтобы неудачный ответ не прошёл незаметно дальше.response.json()возвращает ответ как Python dict. Этот тестовый API возвращает ваши поля и добавляет серверныйid; статус201 Createdподтверждает, что вызов был обработан как новый ресурс.
Проверка Того, Что Реально Получил Сервер
Два независимых сигнала подтверждают, что POST сработал; надёжная проверка использует оба. Тело ответа доказывает доставку содержимого: возвращённые поля плюс новый id означают, что сервер разобрал ваш JSON. Run Details доказывает транспорт: он фиксирует запрос, который браузер реально отправил.
| Сигнал Run Details | Что подтверждает |
|---|---|
| Один POST на jsonplaceholder.typicode.com, статус 201 | Запрос ушёл как POST и сервер создал ресурс |
| Колонка метода показывает POST, не GET | requests.post отправил нужный HTTP-метод |
| Длительность запроса в мс | Время туда-обратно, отдельно от разбора JSON |
| Статус заблокирован или ошибка, без HTTP-кода | Запрос не дошёл до сервера (см. CORS ниже) |
| Packages показывает requests | web.run загрузил пакет requests при импорте |
Когда статус-код 2xx, но в возвращённом теле нет ваших полей — транспорт сработал, а содержимое не дошло. Это расхождение большинство руководств по POST не могут показать, потому что никогда не сопоставляют отправленный запрос и ответ сервера рядом.
json vs data: Скрытая Потеря Данных
Отправьте тот же payload через data= вместо json= — сервер всё равно ответит 2xx, но в возвращённом теле ваших полей не будет. Запрос прошёл; кодировка была неправильной.
| Аргумент | Что отправляет requests | Что разбирает сервер |
|---|---|---|
json={"stars": 3} | JSON-тело и Content-Type: application/json | JSON-объект |
data={"stars": 3} | form-encoded тело и application/x-www-form-urlencoded | поля формы, не JSON |
data=json.dumps({"stars": 3}) | сырая JSON-строка без JSON-заголовка | текст, который сервер может отказаться разбирать как JSON |
Правило: передавайте словарь в json= для любого API, ожидающего JSON-тело, и используйте data= только при отправке HTML-формы. JSON-API, получивший form-кодировку, обычно отвечает 2xx и тихо теряет данные, поэтому проверки статус-кода никогда не достаточно.
Когда POST Блокируется, а не Отклоняется Сервером
Кросс-доменный POST в браузере ведёт себя иначе, чем в серверном скрипте. Поскольку тело — application/json, браузер отправляет CORS preflight OPTIONS перед POST. Если эндпоинт не возвращает Access-Control-Allow-Origin и Access-Control-Allow-Headers, браузер блокирует POST до его отправки.
В этом случае requests выбрасывает ошибку соединения — requests.exceptions.ConnectionError, а не HTTPError — и Run Details отображает запрос как заблокированный или упавший без HTTP-статуса. Статус 403 или 422 — противоположный сигнал: запрос дошёл до сервера, который решил его отклонить.
Правило: отсутствие HTTP-статуса означает сетевую проблему или CORS до сервера; наличие HTTP-статуса означает, что сервер ответил, пусть даже с ошибкой. jsonplaceholder поддерживает CORS, поэтому пример отправляется чисто. Многие публичные API — нет, поэтому одинаковый код requests.post может работать в терминале, но падать в этом рантайме или любом другом Python в браузере.
Частые Ошибки
Ошибка: отправка словаря через data=, когда API ожидает JSON.
Неправильно:
requests.post(url, data={"stars": 3})
Правильно:
requests.post(url, json={"stars": 3})
Почему это происходит: data= form-кодирует словарь и устанавливает form-заголовок Content-Type, поэтому JSON-API не получает JSON-объект и ваши поля пропадают из результата.
Ошибка: двойная сериализация тела.
Неправильно:
requests.post(url, json=json.dumps({"stars": 3}))
Правильно:
requests.post(url, json={"stars": 3})
Почему это происходит: json= уже сериализует значение. Передача готовой строки двойно кодирует её в строку JSON вместо объекта.
Отправка Заголовков и Аутентификации С POST
Большинство write-API требуют аутентификации, которая передаётся в заголовках запроса вместе с телом:
response = requests.post(
url,
json=payload,
headers={"Authorization": "Bearer TOKEN"},
timeout=10,
)
requests объединяет ваши заголовки с Content-Type, который устанавливает для json=, поэтому вы указываете только специфичные для API. Никогда не хардкодьте реальный токен в общем коде — читайте его из переменной окружения. Обратите внимание: кастомный заголовок вроде Authorization сам по себе достаточен для запуска CORS preflight, описанного выше. Для чтения данных смотрите чтение JSON из URL, и оберните вызов в try/except для обработки ошибок соединения.
FAQ
Как отправить POST-запрос в Python?
Вызовите requests.post(url, json=payload) со словарём в качестве нагрузки. Аргумент json= сериализует словарь, устанавливает заголовок Content-Type: application/json и отправляет всё за один вызов. Читайте ответ через response.json().
Почему POST возвращает 200, но сервер игнорирует данные?
Тело было отправлено в неправильном формате — обычно как поля формы через data= вместо JSON через json=. Сервер отвечает 2xx на сам запрос, но никогда не разбирает JSON-объект. Переключитесь на json= и убедитесь, что сервер вернул ваши поля обратно.
Почему POST из requests блокируется в браузере?
Кросс-доменный JSON POST вызывает CORS preflight. Если эндпоинт не возвращает Access-Control-Allow-Origin и Access-Control-Allow-Headers, браузер блокирует запрос до его отправки, и requests выбрасывает ошибку соединения без HTTP-статуса, а не 4xx. Тот же код в терминале может работать.
В чём разница между json и data в requests.post?
json= сериализует словарь в JSON-тело и устанавливает Content-Type: application/json. data= отправляет form-encoded поля или сырую строку без JSON-заголовка. Используйте json= для JSON API и data= для отправки HTML-форм.