Python Requests ile POST ve JSON: cURL Karşılıkları

Yayın tarihi:

14 dk okuma

Acar Diveroli
Yazar: Acar Diveroli
Banttan cURL bayrak kutuları makineye giriyor, sağdan headers=, json= ve files= kutuları çıkıyor; json= kutusu mavi

Bir kargo şirketinin API belgeleri, gönderi oluşturmayı bir cURL komutuyla gösteriyor: curl -X POST https://api.example.com/v1/shipments -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" -d '{"recipient": "Ayse Demir", "weight_kg": 3}'. Komut terminalde 201 Created döndürüyor. Aynı isteği Python'a requests.post(url, data=json.dumps(body), headers={"Authorization": ...}) diye taşıdığınızda sunucu 415 Unsupported Media Type dönüyor. Sebebi şu: data= içine bir dize (string) verildiğinde Requests Content-Type header'ını göndermez. Header'lar isteğin başında giden ad-değer satırlarıdır; Content-Type sunucuya gövdedeki (body) verinin türünü bildirir. Onu ayarlayan -H satırı ise cURL komutunda kaldı.

Bu yazıda json=, data= ve files= argümanlarını, sorgu parametrelerini, header'ları, Bearer token'ı, yanıtın okunmasını ve sık kullanılan cURL seçeneklerinin Requests'teki karşılıklarını anlatıyoruz. Yazının sonunda cURL'de çalışıp Python'da çalışmayan istekler için bir kontrol listesi ve istekleri güvenli biçimde yeniden deneyen küçük bir API istemcisi var. Bütün örnekleri Python 3.13.9, Requests 2.34.2 ve curl 8.21.0 (Windows 11) ile, httpbin.org'da ve aldığı baytları olduğu gibi geri yollayan yerel bir sunucuda denedik.

Python Requests ile POST isteği nasıl gönderilir?

Paketi pip install requests ile kurun. PyPI güncel sürüm olarak 14 Mayıs 2026'da çıkan 2.34.2'yi gösteriyor; bu sürüm Python 3.10 ya da daha yenisini gerektiriyor. Hangi kütüphaneyi seçeceğinizi HTTPX, Requests ve AIOHTTP Karşılaştırması yazısında ele aldık.

JSON gövdeli bir POST tek bir çağrıdır. httpbin.org/post aldığı isteği olduğu gibi geri gönderir:

python
import requests

payload = {"recipient": "Ayse Demir", "weight_kg": 3}
r = requests.post("https://httpbin.org/post", json=payload, timeout=20)

print(r.status_code)                         # 200
print(r.json()["headers"]["Content-Type"])   # application/json
print(r.json()["data"])                      # {"recipient": "Ayse Demir", "weight_kg": 3}
print(r.json()["json"])                      # aynı gövde, dict'e geri çevrilmiş

data alanı gövdeyi gönderildiği hâliyle gösterir: her : ve , işaretinden sonra bir boşluk var. requests.put(), requests.patch() ve requests.delete() aynı argümanları alır. timeout değerini, yani zaman aşımını her zaman verin. Requests'te varsayılan zaman aşımı yoktur; cevap vermeyen bir sunucu betiğinizi süresiz bekletir (zaman aşımı hatalarını Max Retries Exceeded With URL yazısında anlattık).

json=, data= ve files= arasındaki fark nedir?

Her argüman gövdeyi kendi biçiminde kurar ve kendi Content-Type değerini koyar. Aynı dict'i dört yolla gönderdik:

python
import json
import requests

url = "https://httpbin.org/post"
body = {"recipient": "Ayse Demir", "weight_kg": 3}

for label, kwargs in [
    ("json=body", {"json": body}),
    ("data=body", {"data": body}),
    ("data=json.dumps(body)", {"data": json.dumps(body)}),
    ("json= and data=", {"json": body, "data": {"note": "x"}}),
]:
    echo = requests.post(url, timeout=20, **kwargs).json()
    print(f"{label:22} {echo['headers'].get('Content-Type')!s:34} form={echo['form']} json={echo['json']}")
text
json=body              application/json                   form={} json={'recipient': 'Ayse Demir', 'weight_kg': 3}
data=body              application/x-www-form-urlencoded  form={'recipient': 'Ayse Demir', 'weight_kg': '3'} json=None
data=json.dumps(body)  None                               form={} json={'recipient': 'Ayse Demir', 'weight_kg': 3}
json= and data=        application/x-www-form-urlencoded  form={'note': 'x'} json=None

Dört satır şunu gösteriyor:

  • json= dict'i JSON metnine çevirir (serileştirir) ve Content-Type: application/json header'ını ekler. JSON API'leri için bunu kullanın.
  • Dict verilen data= form gönderir ve her değer metne dönüşür: weight_kg sunucuya '3' olarak ulaştı.
  • Dize verilen data= Content-Type göndermez. httpbin gövdeyi yine de ayrıştırdı; header'ı kontrol eden bir API ise 415 ya da 400 döner. Header'ı kendiniz ekleyin.
  • json= ile birlikte data= ya da files= verilirse JSON hata vermeden kaybolur. Requests hızlı başlangıç belgesi, data ya da files verildiğinde json parametresinin yok sayıldığını yazıyor.

data= ile JSON metni göndermeyi, baytların birebir aynı kalması gereken durumlara saklayın. Gövdeyi HMAC ile imzalayan bir API aldığı baytları kontrol eder; json= ise araya boşluk koyar ve ASCII dışı karakterleri kaçış dizisine çevirir ("İzmir", "\u0130zmir" olarak gider). Böyle bir API için baytları kendiniz üretin:

python
import json
import requests

body = {"recipient": "Ayse Demir", "city": "İzmir", "weight_kg": 3}
raw = json.dumps(body, separators=(",", ":"), ensure_ascii=False).encode("utf-8")

r = requests.post("https://httpbin.org/post", data=raw,
                  headers={"Content-Type": "application/json"}, timeout=20)
print(r.json()["data"])  # {"recipient":"Ayse Demir","city":"İzmir","weight_kg":3}

Bu baytların SHA-256 hash'i, aynı UTF-8 dosyasından curl --data-binary @body.json ile gönderilen gövdenin hash'iyle aynı çıktı. CSRF token'lı giriş formlarını Python'da Oturum ve Çerez Yönetimi yazısında anlattık; files= argümanı aşağıda.

cURL komutu Python Requests'e adım adım nasıl çevrilir?

Aynı isteği kargo şirketinin web panelinde, tarayıcının geliştirici araçlarındaki Network sekmesinden kopyaladığınızda birkaç satır fazlası gelir. Böyle bir isteğin nasıl bulunacağını Statik ve Dinamik Sayfalar yazısında anlattık.

bash
curl -X POST "https://api.example.com/v1/shipments?notify=false" \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept-Encoding: gzip, deflate, br" \
  -H "Cookie: session=abc123" \
  --data-raw '{"recipient": "Ayse Demir", "weight_kg": 3}'
  1. Kabuk (shell) işaretlerini temizleyin. Satırları bölen \ işaretlerini (Windows cmd için alınmış kopyada ^) ve tırnakları kaldırın.
  2. Sorgu dizesini params= içine taşıyın. ?notify=false, params={"notify": "false"} olur.
  3. Header'ları ayıklayın. API'nin istediklerini bırakın: Authorization, Accept, API anahtarları. Requests'in kendi yönettiği Accept-Encoding satırını, json= kullanıyorsanız Content-Type satırını da çıkarın. Host ve Content-Length satırlarını hiçbir zaman kopyalamayın; bunları Requests kendisi hesaplar. sec-fetch-* gibi tarayıcı satırlarına nadiren gerek olur. Cookie satırının yeri ise cookies= argümanı ya da bir Session nesnesidir.
  4. Gövde argümanını seçin. JSON gövde json= argümanına gider (gövde imzalanıyorsa birebir baytlarla data=). -d "a=1&b=2" karşılığı data={"a": "1", "b": "2"}, -F karşılığı files=.
  5. Metodu belirleyin. -X başka bir metot belirtmiyorsa -d, --data-raw, --json ve -F POST anlamına gelir; -G ise değerleri bir GET sorgusuna çevirir.
  6. Bir timeout= ekleyin ve r.raise_for_status() çağırın.
  7. Karşılaştırın. cURL komutunu ve Python çağrınızı https://httpbin.org/anything adresine yollayın; httpbin'in geri gönderdiği metodu, URL'yi, header'ları ve gövdeyi karşılaştırın.

Token'ı ortam değişkeninden okuyan son hâl:

python
import os
import requests

r = requests.post(
    "https://api.example.com/v1/shipments",
    params={"notify": "false"},
    headers={"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
    json={"recipient": "Ayse Demir", "weight_kg": 3},
    timeout=20,
)
r.raise_for_status()
print(r.status_code, r.headers.get("Location"))

curlconverter 1-5. adımları kendisi yapar; varsayılan çıktı dili Python Requests'tir. npm ile kurun ve komuttaki curl yerine curlconverter yazın. Yukarıdaki komutu verdiğimizde 4.12.0 sürümü notify değerini params, çerezi cookies=, gövdeyi json= içine taşıdı; Content-Type ve Accept-Encoding satırlarını yoruma aldı. Zaman aşımı eklemedi. README dosyası da bir konuda uyarıyor: komutta yönlendirme ayarı yoksa üretilen kod yönlendirmeleri izler. Tarayıcıdan kopyalanan bir komut oturumunuzun canlı çerezini ve token'ınızı taşır; bu yüzden komutu bir web sitesinde değil, kendi bilgisayarınızda çevirin.

Hangi cURL seçeneği hangi Requests argümanına karşılık gelir?

cURL seçeneğiRequestsFark
-d '{"a":1}' ve Content-Type: application/jsonjson={"a": 1}Requests boşluk ekler; birebir bayt için data=
--json '{"a":1}'json={"a": 1}, headers={"Accept": "application/json"}--json (curl 7.82.0+) Accept değerini de ayarlar
-d "a=1&b=2"data={"a": "1", "b": "2"}Baytlar aynı
--data-binary @body.jsondata=open("body.json", "rb")-d @file satır sonlarını silerdi; bu ikisi korur
-F "file=@report.csv"files={"file": open("report.csv", "rb")}curl parçayı application/octet-stream diye etiketler; Requests türü yalnız üç elemanlı tuple verirseniz ekler
-G --data-urlencode "q=kargo takip"params={"q": "kargo takip"}Sorgu aynı: ?q=kargo+takip
-X PUTrequests.put(url, ...)PATCH ve DELETE için de aynı
-H "Name: value"headers={"Name": "value"}Değerler dize olmalı; int verilirse InvalidHeader fırlatılır
-A "ShipmentSync/1.0"headers={"User-Agent": "ShipmentSync/1.0"}Verilmezse her araç kendi adını gönderir
-b "session=abc123"cookies={"session": "abc123"}Cookie header'ı aynı
-u user:passauth=("user", "pass")Basic header'ı aynı
-LVarsayılancURL'de -L gerekir; Requests'te allow_redirects=False kapatır
--max-redirs 5session.max_redirects = 5Requests varsayılanı: 30
--connect-timeout 3 -m 20timeout=(3.05, 20)-m aktarımın tamamını sınırlar; okuma zaman aşımı ise iki bayt arasındaki bekleme süresidir
-k / --cacert ca.pemverify=False / verify="ca.pem"verify=False yalnız yerel testlerde
-x http://user:pass@pr.proxynet.io:8000proxies={"http": url, "https": url}Bkz. cURL ile Proxy Kullanımı
--compressedGerek yokSıkıştırmayı Requests kendisi halleder
-Irequests.head(url)HEAD isteğinde yönlendirme izlenmez
-i / -vr.headers / r.request.headersAlınan ve gönderilen header'lar

Seçeneklerin hepsi curl kılavuzunda açıklanıyor. Her istekte başka bir proxy kullanmak ise ayrı bir konu (Python'da Proxy'ler Nasıl Döndürülür?).

Sorgu parametreleri ve header'lar nasıl gönderilir?

Sorgu değerlerini params= argümanına dict olarak verin:

python
import requests

params = {"q": "kargo takip", "status": ["pending", "shipped"], "sort": None}
r = requests.get("https://httpbin.org/get", params=params, timeout=20)
print(r.url)  # https://httpbin.org/get?q=kargo+takip&status=pending&status=shipped

Değeri liste olan anahtar tekrarlanır, None olan atlanır; boşlukları ve ASCII dışı karakterleri Requests kendisi kodlar. URL'de zaten bir sorgu dizesi varsa korunur ve params= onun arkasına eklenir. Sonuçlarda sayfa sayfa ilerlemeyi Sayfalama (Pagination) Nedir? yazısında anlattık.

Header'lar, değerleri dize olan bir dict'e yazılır. Token yine ortam değişkeninden okunur:

python
import os
import requests

headers = {
    "Authorization": f"Bearer {os.environ['API_TOKEN']}",
    "Accept": "application/json",
}
r = requests.get("https://httpbin.org/headers", headers=headers, timeout=20)
print(r.json()["headers"]["Authorization"])  # Bearer <token'ınız>

r = requests.get("https://httpbin.org/basic-auth/user/pass", auth=("user", "pass"), timeout=20)
print(r.status_code)  # 200

Her çağrıda gereken header'ları session.headers ile bir requests.Session üzerinde tek seferde tanımlayın. Requests belgelerinde geçen üç kural var; üçünü de testlerimizde doğruladık:

  • .netrc, headers= değerini ezer. Aynı host için tanımlı bir .netrc kaydı (sunucu başına kullanıcı adı ve şifre tutan dosya) Bearer header'ımızı bir Basic header'la değiştirdi. auth= ise ikisini de ezer.
  • Authorization header'ı ilk host'ta kalır. 127.0.0.1 adresinden localhost adresine yönlendirilen istekte header artık yoktu.
  • Değerler dize olmalı. headers={"X-Page": 2} bir InvalidHeader hatası fırlattı.

Kendi User-Agent değerinizi, yani isteği gönderen programın adını bildiren header'ı vermezseniz Requests python-requests/2.34.2 gönderir. Oraya ne yazılacağını User-Agent Nedir? yazısında bulabilirsiniz. Aynı istemcinin gönderdiği header'lar neden birbiriyle tutarlı olmalı, onu da Web Scraping'de Engellenmeden Veri Toplama Yöntemleri yazısında anlattık.

Yanıt nasıl okunur, HTTP hataları nasıl yakalanır?

Response nesnesinde r.status_code, r.headers (büyük-küçük harf ayırmayan bir dict), r.content (ham baytlar), r.text (r.encoding ile çözülmüş baytlar) ve r.json() bulunur. r.text içinde bozuk karakterler görüyorsanız kodlama (encoding) yanlış tahmin edilmiştir (Python Türkçe Karakter Sorunu).

Requests belgeleri bir konuda uyarıyor: r.json() hatasız çalıştı diye istek başarılı olmuş sayılmaz, çünkü sunucu 500 koduyla birlikte JSON biçiminde bir hata gövdesi gönderebilir. Bu yüzden önce durum koduna bakın:

python
import requests

r = requests.get("https://httpbin.org/status/404", timeout=20)
try:
    r.raise_for_status()
except requests.HTTPError as exc:
    print(exc)  # 404 Client Error: NOT FOUND for url: https://httpbin.org/status/404

raise_for_status(), her 4xx ve 5xx cevabında HTTPError fırlatır. Gövde boş ya da HTML olduğunda r.json() bir JSONDecodeError hatası verir; nedenlerini JSONDecodeError: Expecting Value yazısında sıraladık. Hangi kodlarda yeniden denemek gerektiğini de Scraping'de HTTP Hata Kodları yazısında anlattık.

Requests ile dosya nasıl yüklenir ve indirilir?

files= bir multipart/form-data gövdesi kurar. Üç elemanlı bir tuple dosya adını ve parçanın türünü belirler; data= alanları da ayrı parçalar olarak gider. files= varken json= yok sayıldığı için JSON'u kendi parçası olarak gönderin:

python
import json
import requests

with open("report.csv", "rb") as f:
    files = {
        "file": ("report.csv", f, "text/csv"),
        "meta": (None, json.dumps({"source": "warehouse"}), "application/json"),
    }
    r = requests.post("https://httpbin.org/post", files=files, data={"note": "daily"}, timeout=20)

print(r.json()["files"])  # {'file': 'sku,price\n1001,19.90\n'}
print(r.json()["form"])   # {'meta': '{"source": "warehouse"}', 'note': 'daily'}

Dosyayı ikili (binary) kipte, "rb" ile açın. Belgelere göre Requests Content-Length değerini dosyanın bayt sayısından alabilir; metin kipinde açılan dosyada bu değer yanlış çıkabilir. Çok büyük yüklemeler için aynı sayfa, gövdeyi parça parça (stream) gönderen requests-toolbelt paketini öneriyor.

İndirmede stream=True verirseniz büyük bir dosya belleğe bütünüyle alınmadan parça parça diske yazılır:

python
import requests

url = "https://example.com/export.csv"  # kendi dosyanızın adresiyle değiştirin
with requests.get(url, stream=True, timeout=(3.05, 60)) as r:
    r.raise_for_status()
    with open("export.csv", "wb") as f:
        for chunk in r.iter_content(chunk_size=64 * 1024):
            f.write(chunk)

Bir sayfadaki çok sayıda dosyayı indirmeyi Sayfadaki Tüm Resimleri İndirme yazısında anlattık.

cURL'de çalışan istek Requests'te neden farklı cevap alıyor?

Çoğu zaman iki istek aslında birbirinden farklıdır. Şu sırayla kontrol edin:

  1. Yönlendirmeler. cURL, -L olmadan bir 3xx cevabında durur; Requests ise HEAD dışındaki her metotta yönlendirmeyi izler. Hangi araç izlerse izlesin, 301, 302 ya da 303 alan bir POST gövdesiz bir GET'e dönüşür; 307 ve 308 ise POST'u korur. Bu davranış RFC 9110 ile uyumlu. Bir istisna var: -X POST ve -L birlikte verildiğinde cURL 302 sonrasında gövdesiz bir POST gönderdi; --follow (curl 8.16.0+) ise GET'e geçer. r.history listesine bakın ya da allow_redirects=False verip Location header'ını okuyun.
  2. Varsayılan header'lar. curl 8.21.0 User-Agent: curl/8.21.0 ve Accept: */* gönderdi, Accept-Encoding göndermedi. Requests ise python-requests/2.34.2, Accept: */*, Connection: keep-alive ve Accept-Encoding: gzip, deflate gönderdi (brotli kuruluysa br, Python 3.14'te de zstd eklenir). r.request.headers ile karşılaştırın.
  3. HTTP sürümü. Requests yalnız HTTP/1.1 kullanır: bir HTTPS sitesinde r.raw.version değeri 11 döndü. curl ise derlemesi destekliyorsa HTTPS'te varsayılan olarak HTTP/2 ile bağlanır (curl -V çıktısında HTTP2 geçiyorsa destek vardır); hangi sürümün kullanıldığını -w "%{http_version}" yazdırır. Bizim Windows derlememizde bu destek yoktu, bağlantı 1.1 ile kuruldu.
  4. Ortam. Session.trust_env varsayılan değerinde (True) kaldıkça Requests şunları okur: HTTP_PROXY, HTTPS_PROXY ve NO_PROXY ortam değişkenleri, bu değişkenler yoksa Windows ve macOS'ta sistem proxy ayarları, bir de .netrc dosyası. cURL değişkenleri okur (http_proxy yalnız küçük harfle yazılırsa), sistem ayarlarına bakmaz, .netrc dosyasını da yalnız --netrc ile kullanır. Requests'in hangi proxy'yi seçtiğini requests.utils.get_environ_proxies(url) gösterir. Bu değişkenleri wget ile Proxy Kullanımı yazısında açıkladık.
  5. Sertifikalar. Requests certifi paketindeki sertifika listesine güvenir; Windows'ta Schannel ile derlenmiş curl ise Windows sertifika deposuna bakar. TLS trafiğini denetleyen bir şirket proxy'sinin arkasında cURL sorunsuz bağlanırken Requests SSLError verebilir (çözümü Max Retries Exceeded With URL yazısında).
  6. Gövde baytları. json= gövdeyi yeniden serileştirir, -d @file de satır sonlarını siler. API gövdeyi imzalıyorsa iki gövdenin hash'ini alıp karşılaştırın.
  7. Ağdaki gerçek trafik. Yerel bir MITM proxy iki isteği yan yana gösterir.

Her şey aynı olduğu hâlde cevap yine farklıysa site istemcinin kendisine, örneğin TLS el sıkışmasına (handshake) bakıyordur. Header'ları değiştirmek el sıkışmayı değiştirmez. Böyle bir cevabın nasıl okunacağını Cloudflare Scraper yazısı, el sıkışmanın neleri ele verdiğini de TLS Parmak İzi ve JA3 yazısı anlatıyor. Buradan sonraki yol sitenin resmi API'si ya da site sahibinin iznidir; bir betiği tarayıcı gibi gösteren araçları anlatmıyoruz.

Tam örnek: güvenli yeniden deneme yapan küçük bir API istemcisi

Betik, Bearer token'ı ve ortak header'ları tek bir Session üzerinde tutar, GET ile params=, POST ile json= gönderir ve her durum kodunu kontrol eder. Yalnız GET isteğini yeniden dener. MDN POST'u idempotent olmayan, yani tekrarlandığında aynı sonucu vermeyebilen bir metot olarak tanımlar: ikinci kez giden bir POST ikinci bir gönderi kaydı açabilir. urllib3 Retry sınıfı POST'u zaten varsayılan olarak dışarıda bırakıyor; betik bunu yine de açıkça belirtiyor.

python
"""Küçük bir API istemcisi: params ile GET, json= ile POST, yeniden deneme yalnız GET için."""
import os

import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

API = os.environ.get("API_BASE", "https://api.example.com/v1")
TIMEOUT = (3.05, 20)  # bağlantı zaman aşımı, okuma zaman aşımı (saniye)


def make_session():
    session = requests.Session()
    session.headers.update({
        "Authorization": f"Bearer {os.environ['API_TOKEN']}",  # token'ı asla koda gömmeyin
        "Accept": "application/json",
        "User-Agent": "ShipmentSync/1.0 (+https://example.com/contact)",
    })
    retry = Retry(
        total=3,
        backoff_factor=0.5,
        status_forcelist=[502, 503, 504],
        allowed_methods=["GET"],  # tekrarlanan bir POST aynı gönderiyi iki kez oluşturabilir
        raise_on_status=False,    # son yanıtı geri ver, hatayı raise_for_status() bildirsin
    )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount("https://", adapter)
    session.mount("http://", adapter)
    proxy = os.environ.get("PROXY_URL")  # isteğe bağlı, ör. http://user:pass@pr.proxynet.io:8000
    if proxy:
        session.proxies = {"http": proxy, "https": proxy}
        session.trust_env = False  # yoksa HTTPS_PROXY ya da sistem proxy'si session.proxies'in önüne geçer
    return session


def list_shipments(session, status="pending", page=1):
    r = session.get(f"{API}/shipments", params={"status": status, "page": page}, timeout=TIMEOUT)
    r.raise_for_status()
    return r.json()


def create_shipment(session, recipient, weight_kg):
    body = {"recipient": recipient, "weight_kg": weight_kg}
    r = session.post(f"{API}/shipments", json=body, timeout=TIMEOUT)
    r.raise_for_status()
    return r.status_code, r.headers.get("Location"), r.json()


if __name__ == "__main__":
    with make_session() as s:
        try:
            print(list_shipments(s))
            print(create_shipment(s, "Ayse Demir", 3))
        except requests.HTTPError as exc:
            print("API error:", exc)

API'yi taklit eden yerel bir sunucuyla denediğimizde GET listeyi döndürdü, POST ise Location header'ıyla birlikte 201 aldı. Sunucu 503 cevabı verdiğinde GET dört kez gitti (denemeler arasında 0, 1 ve 2 saniye bekleyerek), ardından hatayı raise_for_status() bildirdi. Aynı adrese gönderilen POST ise yalnız bir kez gitti. PROXY_URL ile yerel bir test proxy'si verdiğimizde iki çağrı da çalıştı; yanlış şifreyle 407 Proxy Authentication Required geldi.

urllib3, 503 cevabındaki Retry-After header'ına da varsayılan olarak uyar. 429 cevabını nasıl ele alacağınızı Scraping'de HTTP Hata Kodları yazısında, çok sayıda paralel çağrıyı da Concurrency ve Parallelism yazısında anlattık.

Kullanım alanları

Sık yapılan hatalar

  • Content-Type olmadan data=json.dumps(body). Sunucu gövdenin JSON olduğunu anlayamaz; json=body kullanın.
  • Aynı çağrıda json= ve data=. JSON sessizce düşer.
  • timeout vermemek. Cevap vermeyen tek bir sunucu betiğin tamamını durdurur.
  • Sorgu dizesini elle birleştirmek. Boşluklar ve ASCII dışı karakterler URL'yi bozar.
  • Durum kontrolünden önce r.json() çağırmak. Gövdesi JSON hata mesajı olan bir 500 de sorunsuz ayrıştırılır.
  • Yüklenecek dosyayı metin kipinde açmak. "rb" kullanın.
  • Host ve Content-Length satırlarını kopyalamak. Requests kopyalanan Host değerini değiştirmeden gönderdi; bu yüzden başka bir sunucuyla yaptığımız test hâlâ eski sunucunun adını gönderiyordu. Gövdesiz bir GET'e kopyalanan Content-Length ise sunucumuzu okuma zaman aşımına kadar bekletti.
  • Tarayıcıdan kopyalanan komutları çevrimiçi dönüştürücülere yapıştırmak. Bu komutlar oturum çerezinizi ve token'ınızı içerir.
  • Canlı ortamda verify=False. Sertifika kontrolünü kapatır.
  • POST'u körlemesine yeniden denemek. Zaman aşımından sonra yapılan bir deneme ikinci bir kayıt oluşturabilir.

Karar rehberi

İhtiyaçÖneri
Bir API için JSON gövderequests.post(url, json=data, timeout=20), elle Content-Type eklemeden
İmzalı, bayt bayt aynı gövdeKendi serileştirdiğiniz baytlarla data=, artı Content-Type
Düz form (giriş formu değil)Dict ile data=; giriş ve CSRF için oturum rehberi
Ek alanlarla birlikte dosyafiles= artı data=; JSON ayrı bir application/json parçası olarak
Hızlı cURL çevirisiYukarıdaki tablo ya da kendi bilgisayarınızda curlconverter
cURL'de çalışıyor, Python'da çalışmıyorr.history, r.request.headers, trust_env ve HTTP sürümünü kontrol edin
HTTP/2 ya da async çağrılarHTTPX; yalnız async için AIOHTTP

Sıkça sorulan sorular

Requests ile JSON gönderirken Content-Type header'ını ayarlamak gerekir mi?

json= kullanıyorsanız gerekmez: Requests Content-Type: application/json header'ını kendisi ekler. JSON metnini data= ile gönderdiğinizde ise gerekir, çünkü data= içindeki bir dize bu header olmadan gider ve header'ı kontrol eden bir API 415 Unsupported Media Type ya da 400 döner.

Requests'te json= ile data=json.dumps() arasındaki fark nedir?

İkisi de JSON metni gönderir, ama Content-Type header'ını yalnız json= ekler. Baytlar da farklı olabilir: json= iki nokta üst üste ve virgüllerden sonra boşluk bırakır, ASCII dışı karakterleri de kaçış dizisine çevirir. Varsayılan olarak json= kullanın; API gövdeyi imzalıyorsa kendi ürettiğiniz baytları data= ile gönderin.

Python Requests ile Bearer token nasıl gönderilir?

headers={"Authorization": f"Bearer {token}"} yazın ya da header'ı bir kez session.headers içinde ayarlayın. Token'ı bir ortam değişkeninden okuyun. Bir .netrc dosyasında aynı host için kimlik bilgisi varsa Requests onu kullanır; bu davranışı Session.trust_env = False kapatır.

cURL komutunu Python'a otomatik çevirebilir miyim?

Evet. curlconverter bir cURL komutunu Requests koduna çevirir ve kendi bilgisayarınızda çalışır. Çıktıyı gözden geçirin: zaman aşımı eklemez, ayrıca Requests, cURL komutunun izlemediği yönlendirmeleri izler. Çerez ya da token içeren komutları çevrimiçi dönüştürücülere vermeyin.

Requests yönlendirmeleri izler mi, POST neden GET'e dönüşüyor?

Requests, HEAD dışındaki her metotta yönlendirmeleri izler. 301, 302 ya da 303 sonrasında POST, tarayıcılarda olduğu gibi GET'e dönüşür ve gövdesini kaybeder; 307 ya da 308 sonrasında ise POST olarak kalır. allow_redirects=False verirseniz Requests ilk cevapta durur.

Python Requests HTTP/2 destekler mi?

Hayır. Requests yalnız HTTP/1.1 kullanır; testimizde bir HTTPS sitesi için r.raw.version değeri 11 döndü. HTTPX, httpx[http2] kurulduğunda ve istemci http2=True ile oluşturulduğunda HTTP/2 destekler; bu özellik varsayılan olarak kapalıdır.

Özet

Bir JSON API için requests.post(url, json=data, timeout=20) yazmak yeterlidir: Requests gövdeyi serileştirir ve header'ı ekler. data= form ya da birebir bayt, files= multipart gövde, params= de sorgu dizesi gönderir. Bir cURL komutunun her seçeneği bu argümanlardan birine karşılık gelir. Çevirdikten sonra cevaplar hâlâ farklıysa sırasıyla yönlendirmelere, varsayılan header'lara, HTTP sürümüne ve ortama bakın. Bir API'yi belirli bir ülkeden ya da sabit bir IP adresinden çağırmanız gerekiyorsa seçenekleri proxy hizmetleri sayfamızda karşılaştırabilirsiniz.

ChatGPT'ye sorClaude'a sor