← Kursa Dön
📄 Text · 18 min

HTTP ve API Tüketimi: requests Kütüphanesi

Şimdiye kadar Python ile kendi programlarımızı yazdık — veri işledik, dosya okuduk, veritabanıyla çalıştık. Ama modern yazılım dünyasında programlar tek başına çalışmaz. Hava durumu verisi bir API'dan gelir, ödeme işlemi başka bir servise gönderilir, kullanıcı kimliği üçüncü bir sistemden doğrulanır. Programlar sürekli birbirleriyle konuşur.

Bu konuşmanın dili HTTP'dir ve Python'da bu dili en kolay konuşmanın yolu requests kütüphanesidir. Bu derste requests'i sıfırdan öğrenecek, gerçek API'larla çalışacak ve production seviyesinde HTTP istekleri yapmayı kavrayacaksın.


1. Neden requests?

📞 Analoji: Telefon Görüşmesi

Bir arkadaşını aramak istiyorsun. İki yol var:

  1. Zor yol: Telefon hattının fiziksel sinyallerini kendin yönet — bakır kabloya sinyal gönder, frekansları ayarla, handshake protokolünü kendin uygula.

  2. Kolay yol: Telefonu aç, numarayı çevir, konuş.

Python'da HTTP isteği yapmak da böyle. socket ve http.client modülleriyle her şeyi elle yapabilirsin — ama requests kütüphanesi senin için "telefonu" hazırlar. Sen sadece "numarayı çevir ve konuş" kısmına odaklanırsın.

stdlib vs requests

Python'un yerleşik urllib modülü HTTP istekleri yapabilir ama kullanımı zahmetlidir:

# ❌ urllib ile — zahmetli
import urllib.request
import json

url = "https://jsonplaceholder.typicode.com/posts/1"
req = urllib.request.Request(url)
with urllib.request.urlopen(req) as response:
    data = json.loads(response.read().decode())
    print(data["title"])
# ✅ requests ile — temiz ve okunabilir
import requests

response = requests.get("https://jsonplaceholder.typicode.com/posts/1")
data = response.json()
print(data["title"])

Fark ortada. requests kütüphanesi Python topluluğunun fiili (de facto) HTTP standardıdır. PyPI'deki en çok indirilen paketlerden biridir — haftada 100 milyondan fazla indirme.


2. Kurulum ve İlk İstek

Kurulum

pip install requests

İlk GET İsteği

import requests

# JSONPlaceholder — ücretsiz test API'si
response = requests.get("https://jsonplaceholder.typicode.com/posts/1")

# Status code — isteğin sonucu
print(f"Status: {response.status_code}")  # 200

# Yanıt başlıkları
print(f"Content-Type: {response.headers['Content-Type']}")

# Yanıt gövdesi — JSON olarak
data = response.json()
print(f"Başlık: {data['title']}")
print(f"Yazar ID: {data['userId']}")

Bu kadar. Tek satırda HTTP isteği yaptın, yanıtı aldın ve JSON olarak parse ettin. response nesnesi isteğin tüm detaylarını taşır.


3. HTTP Metodları

REST API'larda dört temel HTTP metodu kullanılır. Her birinin requests'teki karşılığı çok basit:

GET — Veri Çekme

import requests

# Tüm kullanıcıları getir
response = requests.get("https://jsonplaceholder.typicode.com/users")
users = response.json()
print(f"Toplam {len(users)} kullanıcı")

for user in users[:3]:
    print(f"  {user['name']} — {user['email']}")

POST — Yeni Kayıt Oluşturma

import requests

new_post = {
    "title": "Python ile API",
    "body": "requests kütüphanesi harika!",
    "userId": 1
}

response = requests.post(
    "https://jsonplaceholder.typicode.com/posts",
    json=new_post  # Otomatik JSON serialization + Content-Type header
)

print(f"Status: {response.status_code}")  # 201 Created
created = response.json()
print(f"Oluşturulan ID: {created['id']}")

json=new_post parametresi üç şeyi otomatik yapar: dict'i JSON string'e çevirir, Content-Type: application/json header'ını ekler ve body'ye yazar. Elle json.dumps() yapmanız gerekmez.

PUT — Tam Güncelleme

import requests

updated_post = {
    "id": 1,
    "title": "Güncellenmiş Başlık",
    "body": "Yeni içerik",
    "userId": 1
}

response = requests.put(
    "https://jsonplaceholder.typicode.com/posts/1",
    json=updated_post
)
print(f"Status: {response.status_code}")  # 200
print(response.json()["title"])

PATCH — Kısmi Güncelleme

import requests

# Sadece başlığı güncelle
response = requests.patch(
    "https://jsonplaceholder.typicode.com/posts/1",
    json={"title": "Sadece Başlık Değişti"}
)
print(response.json()["title"])

PUT tüm kaynağı değiştirir, PATCH sadece belirtilen alanları değiştirir. Pratikte çoğu API ikisini de kabul eder ama semantik olarak fark budur.

DELETE — Silme

import requests

response = requests.delete("https://jsonplaceholder.typicode.com/posts/1")
print(f"Status: {response.status_code}")  # 200

4. Query Parameters

URL'ye ?key=value&key2=value2 eklemek yerine params parametresini kullan — daha temiz, daha güvenli:

import requests

# ❌ Elle URL birleştirme — özel karakterlerde sorun çıkar
response = requests.get(
    "https://jsonplaceholder.typicode.com/posts?userId=1&_limit=3"
)

# ✅ params kullan — otomatik URL encoding
response = requests.get(
    "https://jsonplaceholder.typicode.com/posts",
    params={
        "userId": 1,
        "_limit": 3
    }
)

print(f"URL: {response.url}")
# https://jsonplaceholder.typicode.com/posts?userId=1&_limit=3

posts = response.json()
for post in posts:
    print(f"  [{post['id']}] {post['title'][:40]}...")

params dict'i otomatik olarak URL query string'ine dönüştürülür. Türkçe karakterler, boşluklar gibi özel karakterler de doğru şekilde encode edilir (URL encoding).

Birden fazla değer göndermek için:

# Aynı parametre birden fazla — /search?tag=python&tag=web
response = requests.get(
    "https://api.example.com/search",
    params=[("tag", "python"), ("tag", "web")]
)

5. Headers ve Özel İstek Yapılandırması

HTTP header'ları isteğin meta bilgilerini taşır — content type, authentication, user agent gibi:

import requests

headers = {
    "Accept": "application/json",
    "User-Agent": "MyPythonApp/1.0",
    "Accept-Language": "tr-TR",
    "X-Custom-Header": "özel-değer"
}

response = requests.get(
    "https://jsonplaceholder.typicode.com/posts/1",
    headers=headers
)

# Yanıt header'larını incele
print(f"Server: {response.headers.get('Server')}")
print(f"Content-Type: {response.headers.get('Content-Type')}")
print(f"Content-Length: {response.headers.get('Content-Length')}")

Header isimleri case-insensitive'dir — response.headers['content-type'] ve response.headers['Content-Type'] aynı şeydir. requests bunu otomatik yönetir.


6. Response Nesnesini Kullanma

requests.get() ve diğer metodlar Response nesnesi döndürür. Bu nesnenin birçok faydalı özelliği var:

import requests

response = requests.get("https://jsonplaceholder.typicode.com/posts/1")

# Status bilgileri
print(f"Status Code: {response.status_code}")        # 200
print(f"Reason: {response.reason}")                   # OK
print(f"OK mu?: {response.ok}")                       # True (200-299 arası)

# İçerik — farklı formatlar
print(f"JSON: {response.json()}")                     # Dict olarak
print(f"Text: {response.text[:100]}")                 # String olarak
print(f"Bytes: {response.content[:50]}")              # Bytes olarak

# Encoding
print(f"Encoding: {response.encoding}")               # utf-8

# Yanıt süreleri
print(f"Yanıt süresi: {response.elapsed}")            # 0:00:00.234567
print(f"Milisaniye: {response.elapsed.total_seconds() * 1000:.0f}ms")

# İstek bilgileri
print(f"İstek URL: {response.url}")
print(f"İstek metodu: {response.request.method}")

# Yönlendirme geçmişi
print(f"Redirect sayısı: {len(response.history)}")

Status Code Kontrolü

import requests

response = requests.get("https://jsonplaceholder.typicode.com/posts/999")

# Yöntem 1: Manuel kontrol
if response.status_code == 200:
    data = response.json()
elif response.status_code == 404:
    print("Kayıt bulunamadı")
elif response.status_code >= 500:
    print("Sunucu hatası")

# Yöntem 2: ok özelliği (200-299 arası True)
if response.ok:
    data = response.json()
else:
    print(f"Hata: {response.status_code}")

# Yöntem 3: raise_for_status() — hata varsa exception fırlatır
try:
    response.raise_for_status()
    data = response.json()
except requests.HTTPError as e:
    print(f"HTTP Hatası: {e}")

raise_for_status() en güvenli yöntemdir — 4xx ve 5xx status kodlarında HTTPError exception'ı fırlatır. Bu sayede hatalı yanıtları try/except ile temiz bir şekilde yakalar ve sessizce geçmezsin.

💡 İpucu: response.json() yanıt JSON değilse JSONDecodeError fırlatır. Her zaman önce status code'u kontrol et, sonra .json() çağır.


7. Authentication (Kimlik Doğrulama)

API'ların çoğu kimlik doğrulama gerektirir. En yaygın yöntemler:

API Key

import requests

# Yöntem 1: Header'da
headers = {"X-API-Key": "your-secret-api-key-123"}
response = requests.get(
    "https://api.example.com/data",
    headers=headers
)

# Yöntem 2: Query parameter'da
response = requests.get(
    "https://api.example.com/data",
    params={"api_key": "your-secret-api-key-123"}
)

API key nereye konulacağı API'nın dokümantasyonuna bağlıdır. Header'da göndermek daha güvenlidir — URL'ler loglarda görünür.

Bearer Token (JWT)

import requests

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

response = requests.get(
    "https://api.example.com/profile",
    headers={"Authorization": f"Bearer {token}"}
)

if response.ok:
    profile = response.json()
    print(f"Kullanıcı: {profile['name']}")

Bearer token genellikle bir login endpoint'inden alınır ve sonraki isteklerde Authorization header'ında gönderilir. JWT (JSON Web Token) bunun en yaygın formatıdır.

Basic Auth

import requests
from requests.auth import HTTPBasicAuth

# Yöntem 1: HTTPBasicAuth
response = requests.get(
    "https://api.example.com/secret",
    auth=HTTPBasicAuth("username", "password")
)

# Yöntem 2: Kısa yol — tuple
response = requests.get(
    "https://api.example.com/secret",
    auth=("username", "password")
)

Basic Auth, kullanıcı adı ve şifreyi Base64 ile encode edip Authorization header'ında gönderir. HTTPS olmadan kullanma — şifre düz metin olarak görünür.

⚠️ Dikkat: API key'lerini, token'ları ve şifreleri asla kaynak koduna yazma. Ortam değişkenleri (os.environ) veya .env dosyası kullan.

import os

API_KEY = os.environ.get("MY_API_KEY")
if not API_KEY:
    raise ValueError("MY_API_KEY ortam değişkeni tanımlı değil!")

8. Dosya Upload ve Download

Dosya Upload

import requests

# Tek dosya
with open("rapor.pdf", "rb") as f:
    response = requests.post(
        "https://api.example.com/upload",
        files={"document": f}
    )

# Dosya adı ve MIME type belirterek
with open("foto.jpg", "rb") as f:
    response = requests.post(
        "https://api.example.com/upload",
        files={"image": ("profil.jpg", f, "image/jpeg")}
    )

# Dosya + form verisi birlikte
with open("foto.jpg", "rb") as f:
    response = requests.post(
        "https://api.example.com/upload",
        files={"image": f},
        data={"description": "Profil fotoğrafı", "user_id": "42"}
    )

files parametresi kullanıldığında requests otomatik olarak Content-Type: multipart/form-data header'ını ekler. json ve files parametrelerini aynı anda kullanamazsın — data ile birleştir.

Dosya Download

import requests

# Küçük dosyalar
response = requests.get("https://example.com/report.pdf")
with open("report.pdf", "wb") as f:
    f.write(response.content)

# Büyük dosyalar — stream ile parça parça indir
response = requests.get(
    "https://example.com/big-video.mp4",
    stream=True
)
with open("video.mp4", "wb") as f:
    for chunk in response.iter_content(chunk_size=8192):
        f.write(chunk)

print(f"İndirildi: {os.path.getsize('video.mp4') / 1024 / 1024:.1f} MB")

stream=True dosyayı belleğe tamamen yüklemez — parça parça (chunk) indirir. GB boyutunda dosyalar için şarttır, yoksa RAM patlar.


9. Session Kullanımı

Aynı sunucuya birden fazla istek yapıyorsan Session nesnesi kullan. Avantajları:

  • Cookie persistence: Oturum çerezleri istekler arasında korunur

  • Connection reuse: TCP bağlantısı yeniden kullanılır (performans)

  • Ortak ayarlar: Header'lar, auth bilgileri tüm isteklerde geçerli

import requests

# ❌ Session olmadan — her istekte yeni bağlantı
for i in range(10):
    response = requests.get(
        "https://api.example.com/data",
        headers={"Authorization": "Bearer my-token"}
    )

# ✅ Session ile — bağlantı ve ayarlar paylaşılır
session = requests.Session()
session.headers.update({
    "Authorization": "Bearer my-token",
    "Accept": "application/json",
    "User-Agent": "MyApp/1.0"
})

for i in range(10):
    response = session.get("https://api.example.com/data")
    # Header'lar otomatik eklenir, bağlantı yeniden kullanılır

Context Manager ile Session

import requests

with requests.Session() as session:
    # Login — cookie otomatik saklanır
    session.post(
        "https://example.com/login",
        json={"username": "admin", "password": "secret"}
    )
    
    # Sonraki istekler — cookie otomatik gönderilir
    profile = session.get("https://example.com/profile")
    settings = session.get("https://example.com/settings")
    
    print(f"Profil: {profile.json()['name']}")
    print(f"Ayarlar: {settings.json()}")
# Session otomatik kapatılır

with bloğu içinde session kullanmak kaynakların düzgün temizlenmesini garanti eder.


10. Timeout ve Hata Yönetimi

Production kodunda timeout şarttır. Timeout olmadan istek sonsuza kadar bekleyebilir ve uygulamanı kilitler.

Timeout Ayarlama

import requests

# Tek timeout — bağlantı + okuma birlikte
try:
    response = requests.get(
        "https://api.example.com/data",
        timeout=5  # 5 saniye
    )
except requests.Timeout:
    print("İstek zaman aşımına uğradı!")

# İki ayrı timeout — (bağlantı, okuma)
response = requests.get(
    "https://api.example.com/data",
    timeout=(3, 10)  # 3sn bağlantı, 10sn okuma
)

timeout=(3, 10) şu anlama gelir: Sunucuya bağlanmak için 3 saniye bekle, yanıt okumak için 10 saniye bekle. İki farklı timeout çünkü bağlantı kurulması hızlı ama yanıt yavaş olabilir.

Kapsamlı Hata Yönetimi

import requests
from requests.exceptions import (
    ConnectionError, Timeout, HTTPError, 
    TooManyRedirects, RequestException
)

def safe_request(url, **kwargs):
    """Güvenli HTTP isteği — tüm hataları ele alır."""
    try:
        response = requests.get(url, timeout=10, **kwargs)
        response.raise_for_status()
        return response.json()
    
    except ConnectionError:
        print(f"Bağlantı hatası: {url} adresine ulaşılamıyor")
    except Timeout:
        print(f"Zaman aşımı: {url} yanıt vermedi")
    except HTTPError as e:
        print(f"HTTP hatası: {e.response.status_code} — {e.response.reason}")
    except TooManyRedirects:
        print(f"Çok fazla yönlendirme: {url}")
    except RequestException as e:
        print(f"Beklenmeyen hata: {e}")
    
    return None

# Kullanım
data = safe_request("https://jsonplaceholder.typicode.com/posts/1")
if data:
    print(f"Başlık: {data['title']}")

Exception hiyerarşisi:

RequestException (en genel)
├── ConnectionError (ağ bağlantısı sorunu)
├── Timeout (zaman aşımı)
├── HTTPError (4xx, 5xx status kodları)
├── TooManyRedirects (sonsuz yönlendirme)
└── URLRequired, MissingSchema, ... (URL formatı hataları)

Genel kural: Önce spesifik hataları yakala, en sonda RequestException ile gerisini yakala.

⚠️ Dikkat: Production kodunda timeout parametresini her zaman belirt. Varsayılan timeout None'dır — yani sonsuza kadar bekler. Bu sunucunun yanıt vermediği durumlarda uygulamanı kilitler.


11. Retry Mekanizması

Geçici hatalar (ağ kesintisi, sunucu aşırı yüklenme) için otomatik yeniden deneme çok önemli:

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

def create_session_with_retry(
    retries=3,
    backoff_factor=0.3,
    status_forcelist=(500, 502, 503, 504)
):
    """Otomatik retry mekanizmalı session oluşturur."""
    session = requests.Session()
    
    retry_strategy = Retry(
        total=retries,              # Toplam deneme sayısı
        backoff_factor=backoff_factor,  # Denemeler arası bekleme çarpanı
        status_forcelist=status_forcelist,  # Bu kodlarda tekrar dene
        allowed_methods=["GET", "POST"],    # Bu metodlarda tekrar dene
    )
    
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("https://", adapter)
    session.mount("http://", adapter)
    
    return session

# Kullanım
session = create_session_with_retry()
response = session.get(
    "https://jsonplaceholder.typicode.com/posts/1",
    timeout=10
)
print(response.json()["title"])

backoff_factor=0.3 denemeler arasında artan bekleme süresi uygular: 0.3s → 0.6s → 1.2s. Bu sayede sunucuya baskı yapmadan tekrar denersin. status_forcelist hangi HTTP kodlarında tekrar deneneceğini belirler — genellikle 5xx (sunucu hataları) eklenir.


12. Gerçek Dünya Projesi: GitHub API

Şimdi öğrendiğimiz her şeyi birleştiren gerçek bir proje yazalım — GitHub API'sından kullanıcı ve repo bilgilerini çeken bir araç:

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

class GitHubClient:
    """GitHub API istemcisi."""
    
    BASE_URL = "https://api.github.com"
    
    def __init__(self, token=None):
        self.session = requests.Session()
        
        # Ortak header'lar
        self.session.headers.update({
            "Accept": "application/vnd.github.v3+json",
            "User-Agent": "PythonGitHubClient/1.0"
        })
        
        # Opsiyonel authentication
        if token:
            self.session.headers["Authorization"] = f"Bearer {token}"
        
        # Retry mekanizması
        retry = Retry(total=3, backoff_factor=0.5, 
                      status_forcelist=[500, 502, 503])
        adapter = HTTPAdapter(max_retries=retry)
        self.session.mount("https://", adapter)
    
    def _request(self, endpoint, params=None):
        """Temel istek metodu — hata yönetimi dahil."""
        url = f"{self.BASE_URL}{endpoint}"
        try:
            response = self.session.get(url, params=params, timeout=10)
            response.raise_for_status()
            return response.json()
        except requests.HTTPError as e:
            if e.response.status_code == 404:
                return None
            elif e.response.status_code == 403:
                print("Rate limit aşıldı! Biraz bekleyin.")
                return None
            raise
        except requests.RequestException as e:
            print(f"İstek hatası: {e}")
            return None
    
    def get_user(self, username):
        """Kullanıcı bilgilerini getir."""
        return self._request(f"/users/{username}")
    
    def get_repos(self, username, sort="updated", per_page=10):
        """Kullanıcının repolarını getir."""
        return self._request(
            f"/users/{username}/repos",
            params={"sort": sort, "per_page": per_page}
        )
    
    def search_repos(self, query, language=None, per_page=5):
        """Repo ara."""
        q = query
        if language:
            q += f" language:{language}"
        data = self._request(
            "/search/repositories",
            params={"q": q, "per_page": per_page, "sort": "stars"}
        )
        return data.get("items", []) if data else []
    
    def close(self):
        """Session'ı kapat."""
        self.session.close()
    
    def __enter__(self):
        return self
    
    def __exit__(self, *args):
        self.close()


# --- Kullanım ---
def main():
    with GitHubClient() as github:
        # 1. Kullanıcı bilgisi
        user = github.get_user("torvalds")
        if user:
            print(f"👤 {user['name']}")
            print(f"   Bio: {user['bio']}")
            print(f"   Followers: {user['followers']:,}")
            print(f"   Public repos: {user['public_repos']}")
        
        print()
        
        # 2. Kullanıcının repoları
        repos = github.get_repos("torvalds", per_page=5)
        if repos:
            print("📦 Son güncellenen repolar:")
            for repo in repos:
                stars = repo['stargazers_count']
                lang = repo.get('language', 'N/A')
                print(f"   ⭐ {stars:>6,}  {repo['name']} ({lang})")
        
        print()
        
        # 3. Repo arama
        results = github.search_repos("web framework", language="python")
        if results:
            print("🔍 Python web framework araması:")
            for repo in results:
                stars = repo['stargazers_count']
                print(f"   ⭐ {stars:>6,}  {repo['full_name']}")
                print(f"            {repo.get('description', '')[:60]}")

if __name__ == "__main__":
    main()

Bu projede neler kullandık:

  • Session — ortak header'lar, bağlantı yeniden kullanımı

  • Authentication — opsiyonel Bearer token

  • Retry — geçici hatalarda otomatik yeniden deneme

  • Timeout — her istekte 10 saniye limit

  • Hata yönetimi — 404, 403 (rate limit), genel hatalar

  • Query parameters — sort, per_page, search query

  • Context manager — kaynakların temiz yönetimi


13. Pagination (Sayfalama)

Çoğu API büyük veri setlerini sayfalara böler. Tüm veriyi çekmek için sayfalar arası dolaşman gerekir:

import requests

def get_all_posts(base_url, per_page=10):
    """Tüm sayfaları dolaşarak tüm kayıtları toplar."""
    all_posts = []
    page = 1
    
    with requests.Session() as session:
        while True:
            response = session.get(
                f"{base_url}/posts",
                params={"_page": page, "_limit": per_page},
                timeout=10
            )
            response.raise_for_status()
            
            posts = response.json()
            if not posts:  # Boş sayfa — bitirdik
                break
            
            all_posts.extend(posts)
            print(f"  Sayfa {page}: {len(posts)} kayıt")
            page += 1
    
    return all_posts

# Kullanım
posts = get_all_posts("https://jsonplaceholder.typicode.com")
print(f"\nToplam: {len(posts)} post")

Bazı API'lar Link header'ında sonraki sayfa URL'sini verir (GitHub gibi):

import requests

def get_paginated_github(url, per_page=30):
    """GitHub tarzı Link header ile pagination."""
    all_items = []
    
    with requests.Session() as session:
        session.headers["Accept"] = "application/vnd.github.v3+json"
        
        while url:
            response = session.get(
                url, params={"per_page": per_page}, timeout=10
            )
            response.raise_for_status()
            all_items.extend(response.json())
            
            # Link header'dan sonraki sayfa URL'sini çıkar
            url = None
            link_header = response.headers.get("Link", "")
            for part in link_header.split(","):
                if 'rel="next"' in part:
                    url = part.split(";")[0].strip(" <>")
    
    return all_items

14. Proxy ve SSL Ayarları

Proxy Kullanımı

import requests

proxies = {
    "http": "http://proxy.example.com:8080",
    "https": "http://proxy.example.com:8080",
}

response = requests.get(
    "https://api.example.com/data",
    proxies=proxies,
    timeout=10
)

SSL Sertifika Kontrolü

import requests

# Varsayılan: SSL doğrulama açık (güvenli)
response = requests.get("https://api.example.com/data")

# SSL doğrulamayı kapat (sadece test/geliştirme için!)
response = requests.get(
    "https://api.example.com/data",
    verify=False  # ⚠️ Production'da KULLANMA
)

# Özel sertifika
response = requests.get(
    "https://api.example.com/data",
    verify="/path/to/custom-ca-bundle.crt"
)

💡 İpucu: verify=False sadece geliştirme ve test ortamında, self-signed sertifika olan sunucularda kullan. Production'da SSL doğrulamasını asla devre dışı bırakma.


15. Yaygın Hatalar ve Best Practices

1. Timeout Koymamak

# ❌ Timeout yok — sunucu yanıt vermezse sonsuza kadar bekler
response = requests.get("https://api.example.com/data")

# ✅ Her zaman timeout koy
response = requests.get("https://api.example.com/data", timeout=10)

2. Status Code Kontrol Etmemek

# ❌ Hata durumunda çökebilir
response = requests.get("https://api.example.com/users/99999")
data = response.json()  # 404 döndüyse JSON olmayabilir!

# ✅ Önce kontrol et
response = requests.get("https://api.example.com/users/99999", timeout=10)
if response.ok:
    data = response.json()
else:
    print(f"Hata: {response.status_code}")

3. Session Kullanmamak

# ❌ 100 ayrı TCP bağlantısı açar
for i in range(100):
    requests.get(f"https://api.example.com/item/{i}")

# ✅ Tek session, bağlantı yeniden kullanımı
with requests.Session() as session:
    for i in range(100):
        session.get(f"https://api.example.com/item/{i}", timeout=10)

4. API Key'i Kodda Tutmak

# ❌ Güvenlik açığı — Git'e push'larsan key sızar
headers = {"Authorization": "Bearer sk-1234567890abcdef"}

# ✅ Ortam değişkeninden oku
import os
token = os.environ["API_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}

5. Rate Limiting'e Saygı Göstermemek

import requests
import time

# ✅ API rate limit'ine uy
def respectful_fetch(urls, delay=0.5):
    """İstekler arası bekleme — API'yı boğma."""
    results = []
    with requests.Session() as session:
        for url in urls:
            response = session.get(url, timeout=10)
            if response.status_code == 429:  # Too Many Requests
                retry_after = int(response.headers.get("Retry-After", 60))
                print(f"Rate limit! {retry_after}sn bekleniyor...")
                time.sleep(retry_after)
                response = session.get(url, timeout=10)
            results.append(response)
            time.sleep(delay)
    return results

Özet

  • requests Python'un fiili HTTP kütüphanesidir — get(), post(), put(), delete() ile tüm HTTP metodlarını destekler.

  • Response nesnesi status code, headers, body (text/json/bytes) ve süre bilgisi taşır. raise_for_status() ile hataları exception'a çevir.

  • Authentication için API Key (header/param), Bearer Token ve Basic Auth yöntemlerini kullanabilirsin — key'leri kodda değil ortam değişkeninde tut.

  • Session aynı sunucuya birden fazla istekte bağlantı yeniden kullanımı, cookie persistence ve ortak ayarlar sağlar.

  • Timeout her zaman belirt, retry mekanizması ekle ve rate limit'e saygı göster — production kodunun üç altın kuralı.

  • Büyük dosya indirirken stream=True, upload için files parametresini kullan.