Python requestsでJSON POSTリクエストを送信する

requests.post(url, json=payload)は1行でサーバーにJSONボディを送信します。json=引数はディクショナリをシリアライズし、Content-Type: application/jsonヘッダーを設定し、リクエストボディを書き込みます。レスポンスはステータスコードと、ほとんどのAPIでサーバーが保存したデータを返すため、呼び出しが意図通り動作したことを確認できます。これはREST APIへの書き込み、Webhook、JSONペイロードを期待するエンドポイントの基本パターンです。

Python requests.postのJSONサンプル

出力:

ここに出力が表示されます...

出力:

Status: 201
Server stored: {'language': 'Python', 'stars': 3, 'id': 101}

このサンプルの動作説明

  1. json=はdictをシリアライズしてContent-Type: application/jsonを設定するため、サーバーはフォームフィールドではなくJSONオブジェクトを受け取ります。
  2. timeout=10は待機時間を制限するため、到達できないエンドポイントは実行をブロックせず素早く失敗します。
  3. raise_for_status()は4xxまたは5xxの応答を例外に変換するため、失敗した応答が次の行に静かに流れることはありません。
  4. response.json()はレスポンスをPythonのdictとして返します。このテストAPIは送信したフィールドを返してサーバー割り当てのidを追加し、ステータス201 Createdは新しいリソースとして処理されたことを確認します。

サーバーが実際に受け取ったものを確認する

2つの独立したシグナルがPOSTの成功を確認します。信頼性の高い確認にはその両方を使います。レスポンスボディはコンテンツが届いたことを証明します:返却されたフィールドと新しいidはサーバーがJSONを解析したことを意味します。Run Detailsはトランスポートを証明します:ブラウザが実際に送信したリクエストを記録します。

Run Detailsのシグナル何を確認するか
jsonplaceholder.typicode.comへのPOST、ステータス201呼び出しがPOSTとして送信され、サーバーがリソースを作成した
メソッド列にGETではなくPOSTが表示されるrequests.postが意図したHTTPメソッドを送信した
リクエストの時間(ms)JSONのパース時間とは別に測定したラウンドトリップ時間
ステータスがブロックまたは失敗、HTTPコードなしリクエストがサーバーに到達しなかった(下記のCORSを参照)
PackagesにrequestsFweb.runがインポート時にrequestsパッケージを読み込んだ

ステータスコードが2xxなのにレスポンスボディにフィールドが含まれていない場合、トランスポートは成功したがコンテンツは届いていません。この区別は、送信したリクエストとサーバーのレスポンスを並べて表示しないほとんどのPOSTチュートリアルでは示せません。

json vs data:データが静かに消えるバグ

同じペイロードをjson=ではなくdata=で送ると、サーバーは依然として2xxで応答しますが、返却されたボディにフィールドが含まれません。リクエスト自体は正常でしたが、エンコーディングが間違っていました。

引数requestsが送信するものサーバーが解析するもの
json={"stars": 3}JSONボディとContent-Type: application/jsonJSONオブジェクト
data={"stars": 3}form-encodedボディとapplication/x-www-form-urlencodedフォームフィールド(JSONではない)
data=json.dumps({"stars": 3})JSONヘッダーのない生のJSON文字列JSONとして解析を拒否する可能性があるテキスト

ルール:JSONボディを期待するAPIにはjson=にdictを渡し、data=はHTMLフォームを送信するときのみ使用します。form-encodingを受け取ったJSON APIは通常2xxで応答してデータを静かに破棄するため、ステータスコードの確認だけでは不十分です。

ブラウザのPOSTがリジェクトではなくブロックされる場合

クロスオリジンのPOSTはブラウザではサーバースクリプトと異なる動作をします。ボディがapplication/jsonのため、ブラウザはPOSTの前にCORSプリフライトOPTIONSを送信します。エンドポイントがAccess-Control-Allow-OriginAccess-Control-Allow-Headersを返さない場合、ブラウザはPOSTが送信される前にブロックします。

この場合、requestsはHTTPErrorではなくrequests.exceptions.ConnectionErrorのような接続エラーを発生させ、Run DetailsはリクエストをHTTPステータスなしでブロックまたは失敗としてマークします。403や422は逆のシグナルです:リクエストがサーバーに到達し、サーバーが拒否を選択しました。

ルール:HTTPステータスがない場合はサーバー前のネットワークまたはCORSの問題、HTTPステータスがある場合はサーバーが応答したことを意味します(エラーでも同様)。jsonplaceholderはCORS対応のため、サンプルはクリーンに送信されます。多くのパブリックAPIはCORS対応ではないため、同じrequests.postコードがターミナルでは動作しても、このランタイムやブラウザ内Pythonでは失敗することがあります。

よくある間違い

間違い:APIがJSONを期待しているのにdata=でdictを送る。

誤り:

requests.post(url, data={"stars": 3})

正しい:

requests.post(url, json={"stars": 3})

なぜ起きるか:data=はdictをform-encodeしてContent-Typeにフォームを設定するため、JSON APIはJSONオブジェクトを受け取れず、フィールドが結果から消えます。

間違い:ボディを二重にシリアライズする。

誤り:

requests.post(url, json=json.dumps({"stars": 3}))

正しい:

requests.post(url, json={"stars": 3})

なぜ起きるか:json=はすでに値をシリアライズします。構築済みの文字列を渡すと、オブジェクトではなくJSON文字列として二重エンコードされます。

POSTにヘッダーと認証を追加する

ほとんどの書き込みAPIは認証を必要とし、ボディの横のリクエストヘッダーで送信されます:

response = requests.post(
    url,
    json=payload,
    headers={"Authorization": "Bearer TOKEN"},
    timeout=10,
)

requestsはあなたのヘッダーをjson=のために設定するContent-Typeとマージするため、APIに固有のものだけを指定します。共有コードに実際のトークンをハードコードしないでください。環境変数から読み取ります。Authorizationのようなカスタムヘッダーは、上記のCORSプリフライトを引き起こすのに十分です。読み取り側についてはURLからJSONを読み込むを参照し、接続エラーを処理するために呼び出しをtry/exceptで包んでください。

FAQ

PythonでPOSTリクエストを送るには?

ディクショナリをペイロードとしてrequests.post(url, json=payload)を呼び出します。json=引数はdictをシリアライズし、Content-Type: application/jsonヘッダーを設定して、1回の呼び出しで送信します。response.json()でレスポンスを読み取ります。

POSTが200を返しているのにサーバーがデータを無視するのはなぜ?

ボディが間違った形式で送信されています。通常、json=ではなくdata=でフォームフィールドとして送られています。サーバーはリクエスト自体には2xxで応答しますが、JSONオブジェクトを解析することはありません。json=に切り替えて、サーバーがフィールドを返してくれることを確認してください。

ブラウザでrequestsのPOSTがブロックされるのはなぜ?

クロスオリジンのJSON POSTはCORSプリフライトを引き起こします。エンドポイントがAccess-Control-Allow-OriginAccess-Control-Allow-Headersを返さない場合、ブラウザはリクエストが送信される前にブロックし、requestsは4xxではなくHTTPステータスなしの接続エラーを発生させます。同じコードはターミナルでは動作する可能性があります。

requests.postのjsonとdataの違いは?

json=はdictをJSONボディにシリアライズしてContent-Type: application/jsonを設定します。data=はJSONヘッダーなしでform-encodedフィールドまたは生の文字列を送信します。JSON APIにはjson=を、HTMLフォーム送信にはdata=を使用します。