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:
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:
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']}")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=NoneDört satır şunu gösteriyor:
json=dict'i JSON metnine çevirir (serileştirir) veContent-Type: application/jsonheader'ı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_kgsunucuya'3'olarak ulaştı. - Dize verilen
data=Content-Typegöndermez. httpbin gövdeyi yine de ayrıştırdı; header'ı kontrol eden bir API ise415ya da400döner. Header'ı kendiniz ekleyin. json=ile birliktedata=ya dafiles=verilirse JSON hata vermeden kaybolur. Requests hızlı başlangıç belgesi,dataya dafilesverildiğindejsonparametresinin 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:
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.
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}'- Kabuk (shell) işaretlerini temizleyin. Satırları bölen
\işaretlerini (Windowscmdiçin alınmış kopyada^) ve tırnakları kaldırın. - Sorgu dizesini
params=içine taşıyın.?notify=false,params={"notify": "false"}olur. - Header'ları ayıklayın. API'nin istediklerini bırakın:
Authorization,Accept, API anahtarları. Requests'in kendi yönettiğiAccept-Encodingsatırını,json=kullanıyorsanızContent-Typesatırını da çıkarın.HostveContent-Lengthsatırlarını hiçbir zaman kopyalamayın; bunları Requests kendisi hesaplar.sec-fetch-*gibi tarayıcı satırlarına nadiren gerek olur.Cookiesatırının yeri isecookies=argümanı ya da birSessionnesnesidir. - Gövde argümanını seçin. JSON gövde
json=argümanına gider (gövde imzalanıyorsa birebir baytlarladata=).-d "a=1&b=2"karşılığıdata={"a": "1", "b": "2"},-Fkarşılığıfiles=. - Metodu belirleyin.
-Xbaşka bir metot belirtmiyorsa-d,--data-raw,--jsonve-FPOST anlamına gelir;-Gise değerleri bir GET sorgusuna çevirir. - Bir
timeout=ekleyin ver.raise_for_status()çağırın. - Karşılaştırın. cURL komutunu ve Python çağrınızı
https://httpbin.org/anythingadresine 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:
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ği | Requests | Fark |
|---|---|---|
-d '{"a":1}' ve Content-Type: application/json | json={"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.json | data=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 PUT | requests.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:pass | auth=("user", "pass") | Basic header'ı aynı |
-L | Varsayılan | cURL'de -L gerekir; Requests'te allow_redirects=False kapatır |
--max-redirs 5 | session.max_redirects = 5 | Requests varsayılanı: 30 |
--connect-timeout 3 -m 20 | timeout=(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.pem | verify=False / verify="ca.pem" | verify=False yalnız yerel testlerde |
-x http://user:pass@pr.proxynet.io:8000 | proxies={"http": url, "https": url} | Bkz. cURL ile Proxy Kullanımı |
--compressed | Gerek yok | Sıkıştırmayı Requests kendisi halleder |
-I | requests.head(url) | HEAD isteğinde yönlendirme izlenmez |
-i / -v | r.headers / r.request.headers | Alı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:
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=shippedDeğ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:
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) # 200Her ç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.netrckaydı (sunucu başına kullanıcı adı ve şifre tutan dosya)Bearerheader'ımızı birBasicheader'la değiştirdi.auth=ise ikisini de ezer.- Authorization header'ı ilk host'ta kalır.
127.0.0.1adresindenlocalhostadresine yönlendirilen istekte header artık yoktu. - Değerler dize olmalı.
headers={"X-Page": 2}birInvalidHeaderhatası 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:
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/404raise_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:
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:
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:
- Yönlendirmeler. cURL,
-Lolmadan bir3xxcevabında durur; Requests iseHEADdışındaki her metotta yönlendirmeyi izler. Hangi araç izlerse izlesin,301,302ya da303alan bir POST gövdesiz bir GET'e dönüşür;307ve308ise POST'u korur. Bu davranış RFC 9110 ile uyumlu. Bir istisna var:-X POSTve-Lbirlikte verildiğinde cURL302sonrasında gövdesiz bir POST gönderdi;--follow(curl 8.16.0+) ise GET'e geçer.r.historylistesine bakın ya daallow_redirects=FalseveripLocationheader'ını okuyun. - Varsayılan header'lar. curl 8.21.0
User-Agent: curl/8.21.0veAccept: */*gönderdi,Accept-Encodinggöndermedi. Requests isepython-requests/2.34.2,Accept: */*,Connection: keep-aliveveAccept-Encoding: gzip, deflategönderdi (brotli kuruluysabr, Python 3.14'te dezstdeklenir).r.request.headersile karşılaştırın. - HTTP sürümü. Requests yalnız HTTP/1.1 kullanır: bir HTTPS sitesinde
r.raw.versiondeğeri11döndü. curl ise derlemesi destekliyorsa HTTPS'te varsayılan olarak HTTP/2 ile bağlanır (curl -VçıktısındaHTTP2geç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.1ile kuruldu. - Ortam.
Session.trust_envvarsayılan değerinde (True) kaldıkça Requests şunları okur:HTTP_PROXY,HTTPS_PROXYveNO_PROXYortam değişkenleri, bu değişkenler yoksa Windows ve macOS'ta sistem proxy ayarları, bir de.netrcdosyası. cURL değişkenleri okur (http_proxyyalnız küçük harfle yazılırsa), sistem ayarlarına bakmaz,.netrcdosyasını da yalnız--netrcile kullanır. Requests'in hangi proxy'yi seçtiğinirequests.utils.get_environ_proxies(url)gösterir. Bu değişkenleri wget ile Proxy Kullanımı yazısında açıkladık. - Sertifikalar. Requests
certifipaketindeki 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 RequestsSSLErrorverebilir (çözümü Max Retries Exceeded With URL yazısında). - Gövde baytları.
json=gövdeyi yeniden serileştirir,-d @filede satır sonlarını siler. API gövdeyi imzalıyorsa iki gövdenin hash'ini alıp karşılaştırın. - 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.
"""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ı
- Sitenin sunduğu bir API'den ürün ya da fiyat verisi almak (veri kazıma).
- Network sekmesinde bulduğunuz bir JSON endpoint'ini, sayfayı render etmek yerine doğrudan çağırmak (Statik ve Dinamik Sayfalar).
- API sonuçlarının arkasındaki sayfaları siteyi yormayan bir hızla taramak (Python ile Web Crawler Nasıl Yazılır?).
- Kendi webhook'unuzu ya da iç servisinizi bir arayüz aracı yerine betikle test etmek (Postman Proxy Ayarları).
- Bir API'nin başka bir ülkeye verdiği cevabı o ülkedeki bir Residential Proxy üzerinden kontrol etmek.
- Yalnız kayıtlı IP adreslerini kabul eden bir API'yi tek ve sabit bir çıkış IP'sinden çağırmak (API için Statik IP).
- Aynı isteği Node.js'te fetch ya da Axios ile göndermek (JavaScript'te cURL).
Sık yapılan hatalar
Content-Typeolmadandata=json.dumps(body). Sunucu gövdenin JSON olduğunu anlayamaz;json=bodykullanın.- Aynı çağrıda
json=vedata=. JSON sessizce düşer. timeoutvermemek. 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 bir500de sorunsuz ayrıştırılır. - Yüklenecek dosyayı metin kipinde açmak.
"rb"kullanın. HostveContent-Lengthsatırlarını kopyalamak. Requests kopyalananHostdeğ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 kopyalananContent-Lengthise 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övde | requests.post(url, json=data, timeout=20), elle Content-Type eklemeden |
| İmzalı, bayt bayt aynı gövde | Kendi 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 dosya | files= artı data=; JSON ayrı bir application/json parçası olarak |
| Hızlı cURL çevirisi | Yukarıdaki tablo ya da kendi bilgisayarınızda curlconverter |
| cURL'de çalışıyor, Python'da çalışmıyor | r.history, r.request.headers, trust_env ve HTTP sürümünü kontrol edin |
| HTTP/2 ya da async çağrılar | HTTPX; 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.




