Kurs Python · Moduł 4: Dane, API i bazy danych

HTTP - protokół wymiany danych

9 min czytania
W tej lekcji14

Witaj ponownie! Darwin tutaj.

W poprzedniej lekcji nauczyłeś/aś się formatów danych - jak przechowywać informacje (JSON, CSV, XML, YAML). Teraz czas na HTTP - protokół, który pozwala przesyłać te dane przez internet!

Analogia Safari: Formaty danych to język, w którym zapisujesz obserwacje. HTTP to system kurierski - sposób, w jaki wysyłasz te obserwacje między stacjami badawczymi w różnych częściach Afryki!

Czym jest HTTP?

HTTP (HyperText Transfer Protocol) to protokół komunikacyjny - zbiór zasad, według których komputery wymieniają się danymi przez internet.

Jak to działa:

  1. Klient (np. przeglądarka, aplikacja Python) wysyła zapytanie (request)
  2. Serwer (np. strona, API) przetwarza zapytanie
  3. Serwer odsyła odpowiedź (response) z danymi lub informacją o błędzie
1[KLIENT]  ---(HTTP REQUEST)--->  [SERWER]
2   ↓                                  ↓
3   ↓                              Przetwarza
4   ↓                                  ↓
5   ↓  <---(HTTP RESPONSE)---  [SERWER]

Przykład Safari: Wysyłasz prośbę do centralnej bazy danych w Nairobi: "Podaj mi dane o lwach w Serengeti". Serwer odpowiada: "Oto 120 lwów w formie JSON"!

Metody HTTP - rodzaje zapytań

HTTP ma różne metody (czasem nazywane "czasownikami") - określają CO chcesz zrobić:

MetodaPrzeznaczenieAnalogia Safari
GETPobierz dane"Pokaż mi listę gatunków"
POSTUtwórz nowy zasób"Dodaj nową obserwację słonia"
PUTZaktualizuj cały zasób"Zastąp cały profil lwa nowymi danymi"
PATCHZaktualizuj część zasobu"Zmień tylko populację goryla"
DELETEUsuń zasób"Usuń błędną obserwację"

Najważniejsze: GET i POST - używane w 90% przypadków!

GET - pobieranie danych

1GET /api/species/lion HTTP/1.1
2Host: safari-api.com

Cechy:

  • Nie modyfikuje danych na serwerze
  • Parametry w URL (?name=lion&habitat=savanna)
  • Można zapisać w zakładkach
  • Bezpieczna do wielokrotnego wywołania

POST - tworzenie/wysyłanie danych

1POST /api/observations HTTP/1.1
2Host: safari-api.com
3Content-Type: application/json
4
5{
6  "species": "Panthera leo",
7  "count": 12,
8  "location": "Serengeti"
9}

Cechy:

  • Wysyła dane w ciele zapytania (body), nie w URL
  • Może modyfikować dane na serwerze
  • Nie zapisuje się w historii przeglądarki
  • Może tworzyć nowe zasoby

Kody statusu HTTP - odpowiedzi serwera

Serwer zawsze odpowiada kodem statusu - liczbą mówiącą czy operacja się powiodła:

2xx - Sukces

  • 200 OK - zapytanie udane, oto dane
  • 201 Created - zasób został utworzony
  • 204 No Content - sukces, ale brak danych do zwrócenia

3xx - Przekierowanie

  • 301 Moved Permanently - zasób przeniesiony na stałe
  • 302 Found - tymczasowe przekierowanie

4xx - Błąd klienta

  • 400 Bad Request - błędne zapytanie (np. zły JSON)
  • 401 Unauthorized - wymagane uwierzytelnienie
  • 403 Forbidden - brak dostępu
  • 404 Not Found - zasób nie istnieje
  • 429 Too Many Requests - zbyt wiele zapytań

5xx - Błąd serwera

  • 500 Internal Server Error - błąd serwera
  • 502 Bad Gateway - problem z serwerem pośredniczącym
  • 503 Service Unavailable - serwer niedostępny

Analogia Safari: Kod 200 = "Znalazłem lwa!", 404 = "Nie ma tu żadnego lwa", 500 = "Mój radiotelefon się zepsuł"

Python requests - biblioteka HTTP

requests to najpopularniejsza biblioteka Python do HTTP - prosta, elegancka, potężna!

Instalacja

1pip install requests

Podstawowe użycie - GET request

1import requests
2
3# Pobierz dane
4response = requests.get("https://api.github.com/users/octocat")
5
6# Status code
7print(response.status_code)  # 200
8
9# Dane jako JSON
10data = response.json()
11print(data["login"])  # "octocat"
12print(data["name"])   # "The Octocat"
13
14# Dane jako tekst
15print(response.text)  # Surowy JSON jako string
16
17# Nagłówki odpowiedzi
18print(response.headers["Content-Type"])  # "application/json"

Sprawdzanie sukcesu

1import requests
2
3response = requests.get("https://api.github.com/users/nonexistent")
4
5# Metoda 1: Sprawdź status code
6if response.status_code == 200:
7    print("Sukces!")
8elif response.status_code == 404:
9    print("Nie znaleziono")
10
11# Metoda 2: Użyj response.ok (True dla 2xx)
12if response.ok:
13    data = response.json()
14    print(data)
15else:
16    print(f"Błąd: {response.status_code}")
17
18# Metoda 3: Rzuć wyjątek przy błędzie
19try:
20    response.raise_for_status()  # Rzuca HTTPError dla 4xx/5xx
21    data = response.json()
22except requests.HTTPError as e:
23    print(f"HTTP Error: {e}")

Parametry URL - query parameters

GET requests często używają parametrów w URL:

1import requests
2
3# Ręcznie w URL
4response = requests.get("https://api.example.com/species?habitat=savanna&endangered=true")
5
6# Lepiej: słownik params (automatyczne kodowanie)
7params = {
8    "habitat": "savanna",
9    "endangered": "true",
10    "min_population": 100
11}
12
13response = requests.get("https://api.example.com/species", params=params)
14# URL: https://api.example.com/species?habitat=savanna&endangered=true&min_population=100
15
16print(response.url)  # Zobacz wygenerowany URL

Nagłówki (Headers)

Nagłówki to metadane zapytania - informacje o kliencie, oczekiwanym formacie, autoryzacji:

1import requests
2
3# Własne nagłówki
4headers = {
5    "User-Agent": "Safari-Data-Collector/1.0",
6    "Accept": "application/json",
7    "Authorization": "Bearer YOUR_API_KEY_HERE"
8}
9
10response = requests.get("https://api.example.com/species", headers=headers)
11
12# Sprawdź nagłówki odpowiedzi
13print(response.headers["Content-Type"])
14print(response.headers["Date"])

Popularne nagłówki:

  • User-Agent - identyfikacja klienta
  • Accept - oczekiwany format odpowiedzi
  • Content-Type - format wysyłanych danych
  • Authorization - token/klucz API

POST request - wysyłanie danych

JSON payload

1import requests
2
3# Dane do wysłania
4new_observation = {
5    "species": "Panthera leo",
6    "location": "Serengeti",
7    "count": 12,
8    "date": "2024-01-15",
9    "observer": "Darwin"
10}
11
12# POST z JSON
13response = requests.post(
14    "https://api.safari.com/observations",
15    json=new_observation  # Automatycznie: Content-Type: application/json
16)
17
18if response.status_code == 201:  # Created
19    created_data = response.json()
20    print(f"Utworzono obserwację ID: {created_data['id']}")
21else:
22    print(f"Błąd: {response.status_code}")

Form data (application/x-www-form-urlencoded)

1import requests
2
3# Dane formularza
4form_data = {
5    "species": "Loxodonta africana",
6    "count": "35"
7}
8
9response = requests.post(
10    "https://api.safari.com/quick-report",
11    data=form_data  # Content-Type: application/x-www-form-urlencoded
12)

PUT, PATCH, DELETE

1import requests
2
3# PUT - zastąp cały zasób
4updated_species = {
5    "scientific_name": "Panthera leo",
6    "population": 125,  # Zaktualizowana
7    "habitat": "sawanna"
8}
9
10response = requests.put(
11    "https://api.safari.com/species/lion",
12    json=updated_species
13)
14
15# PATCH - zmień tylko część
16partial_update = {
17    "population": 125  # Tylko populacja
18}
19
20response = requests.patch(
21    "https://api.safari.com/species/lion",
22    json=partial_update
23)
24
25# DELETE - usuń zasób
26response = requests.delete("https://api.safari.com/observations/12345")
27
28if response.status_code == 204:  # No Content
29    print("Usunięto pomyślnie")

Obsługa błędów

1import requests
2from requests.exceptions import RequestException, Timeout, ConnectionError
3
4def fetch_species_data(species_id: str):
5    """Pobierz dane gatunku z obsługą błędów"""
6    try:
7        response = requests.get(
8            f"https://api.safari.com/species/{species_id}",
9            timeout=5  # Timeout po 5 sekundach
10        )
11
12        # Sprawdź status
13        response.raise_for_status()
14
15        # Przetwórz dane
16        return response.json()
17
18    except Timeout:
19        print("⏱Timeout - serwer nie odpowiedział w czasie")
20        return None
21
22    except ConnectionError:
23        print("Błąd połączenia - sprawdź internet")
24        return None
25
26    except requests.HTTPError as e:
27        print(f"HTTP Error {response.status_code}: {e}")
28        return None
29
30    except RequestException as e:
31        print(f"Błąd zapytania: {e}")
32        return None
33
34# Użycie
35data = fetch_species_data("lion")
36if data:
37    print(f"Gatunek: {data['name']}")

Safari przykład - API klient do zarządzania obserwacjami

1import requests
2from typing import List, Dict, Optional
3from datetime import datetime
4
5class SafariAPIClient:
6    """Klient API do systemu obserwacji Safari"""
7
8    def __init__(self, base_url: str, api_key: str):
9        self.base_url = base_url.rstrip("/")
10        self.api_key = api_key
11        self.session = requests.Session()  # Reużywalne połączenie
12
13        # Domyślne nagłówki dla wszystkich zapytań
14        self.session.headers.update({
15            "User-Agent": "Safari-Data-Collector/2.0",
16            "Authorization": f"Bearer {self.api_key}",
17            "Accept": "application/json"
18        })
19
20    def get_species(self, species_id: str) -> Optional[Dict]:
21        """Pobierz dane gatunku"""
22        try:
23            response = self.session.get(
24                f"{self.base_url}/species/{species_id}",
25                timeout=10
26            )
27            response.raise_for_status()
28            return response.json()
29
30        except requests.RequestException as e:
31            print(f"Błąd pobierania gatunku: {e}")
32            return None
33
34    def search_species(self, habitat: Optional[str] = None,
35                      endangered: Optional[bool] = None,
36                      min_population: Optional[int] = None) -> List[Dict]:
37        """Wyszukaj gatunki z filtrami"""
38        params = {}
39        if habitat:
40            params["habitat"] = habitat
41        if endangered is not None:
42            params["endangered"] = str(endangered).lower()
43        if min_population:
44            params["min_population"] = min_population
45
46        try:
47            response = self.session.get(
48                f"{self.base_url}/species",
49                params=params,
50                timeout=10
51            )
52            response.raise_for_status()
53            return response.json()
54
55        except requests.RequestException as e:
56            print(f"Błąd wyszukiwania: {e}")
57            return []
58
59    def create_observation(self, species: str, location: str,
60                          count: int, notes: str = "") -> Optional[Dict]:
61        """Utwórz nową obserwację"""
62        observation_data = {
63            "species": species,
64            "location": location,
65            "count": count,
66            "date": datetime.now().isoformat(),
67            "notes": notes
68        }
69
70        try:
71            response = self.session.post(
72                f"{self.base_url}/observations",
73                json=observation_data,
74                timeout=10
75            )
76            response.raise_for_status()
77
78            if response.status_code == 201:
79                created = response.json()
80                print(f"Utworzono obserwację ID: {created.get('id')}")
81                return created
82            else:
83                return response.json()
84
85        except requests.RequestException as e:
86            print(f"Błąd tworzenia obserwacji: {e}")
87            return None
88
89    def update_species_population(self, species_id: str,
90                                 new_population: int) -> bool:
91        """Zaktualizuj populację gatunku (PATCH)"""
92        try:
93            response = self.session.patch(
94                f"{self.base_url}/species/{species_id}",
95                json={"population": new_population},
96                timeout=10
97            )
98            response.raise_for_status()
99            print(f"Zaktualizowano populację {species_id} → {new_population}")
100            return True
101
102        except requests.RequestException as e:
103            print(f"Błąd aktualizacji: {e}")
104            return False
105
106    def delete_observation(self, observation_id: str) -> bool:
107        """Usuń obserwację"""
108        try:
109            response = self.session.delete(
110                f"{self.base_url}/observations/{observation_id}",
111                timeout=10
112            )
113            response.raise_for_status()
114
115            if response.status_code == 204:
116                print(f"Usunięto obserwację {observation_id}")
117                return True
118
119            return False
120
121        except requests.RequestException as e:
122            print(f"Błąd usuwania: {e}")
123            return False
124
125    def get_statistics(self) -> Optional[Dict]:
126        """Pobierz statystyki (z parametrami query)"""
127        params = {
128            "include_endangered": "true",
129            "group_by": "habitat"
130        }
131
132        try:
133            response = self.session.get(
134                f"{self.base_url}/statistics",
135                params=params,
136                timeout=15
137            )
138            response.raise_for_status()
139            return response.json()
140
141        except requests.RequestException as e:
142            print(f"Błąd pobierania statystyk: {e}")
143            return None
144
145    def close(self):
146        """Zamknij sesję"""
147        self.session.close()
148
149
150# === DEMONSTRACJA ===
151
152print("=== SAFARI API CLIENT ===\n")
153
154# Inicjalizacja klienta
155client = SafariAPIClient(
156    base_url="https://api.safari-data.org/v1",
157    api_key="demo_key_12345"
158)
159
160# GET - pobierz gatunek
161print("1. Pobieranie danych lwa...")
162lion_data = client.get_species("lion")
163if lion_data:
164    print(f"   Gatunek: {lion_data.get('common_name')}")
165    print(f"   Populacja: {lion_data.get('population')}")
166
167# GET z parametrami - wyszukiwanie
168print("\n2. Wyszukiwanie zagrożonych gatunków w sawannie...")
169endangered = client.search_species(habitat="savanna", endangered=True)
170print(f"   Znaleziono {len(endangered)} gatunków")
171
172# POST - tworzenie obserwacji
173print("\n3. Tworzenie nowej obserwacji...")
174new_obs = client.create_observation(
175    species="Panthera leo",
176    location="Serengeti North",
177    count=8,
178    notes="Pride with 2 cubs"
179)
180
181# PATCH - aktualizacja
182print("\n4. Aktualizacja populacji...")
183client.update_species_population("lion", 125)
184
185# DELETE - usuwanie
186print("\n5. Usuwanie błędnej obserwacji...")
187client.delete_observation("obs_12345")
188
189# GET ze statystykami
190print("\n6. Pobieranie statystyk...")
191stats = client.get_statistics()
192if stats:
193    print(f"   Łączna liczba gatunków: {stats.get('total_species')}")
194
195# Zamknij sesję
196client.close()
197print("\nSesja zakończona")

Session - wydajność dla wielu zapytań

Jeśli robisz wiele zapytań do tego samego API, użyj Session - reużywa połączenie TCP:

1import requests
2
3# BEZ Session - każde zapytanie = nowe połączenie
4for i in range(100):
5    response = requests.get("https://api.example.com/data")
6
7# Z Session - jedno połączenie, szybsze!
8session = requests.Session()
9session.headers.update({"Authorization": "Bearer token123"})
10
11for i in range(100):
12    response = session.get("https://api.example.com/data")
13
14session.close()

Timeouty - nie czekaj w nieskończoność

Zawsze używaj timeout, aby zapytanie nie wisiało w nieskończoność:

1import requests
2
3# Timeout 5 sekund
4response = requests.get("https://slow-api.com/data", timeout=5)
5
6# Różne timeouty: (connect_timeout, read_timeout)
7response = requests.get("https://api.com/data", timeout=(3, 10))
8# 3s na połączenie, 10s na odczyt danych

Podsumowanie

W tej lekcji nauczyłeś/aś się:

  • Czym jest HTTP i jak działa (klient → serwer → odpowiedź)
  • Metody HTTP: GET, POST, PUT, PATCH, DELETE
  • Kody statusu: 200 OK, 404 Not Found, 500 Server Error
  • Biblioteka requests: requests.get(), requests.post()
  • Parametry URL (params=), nagłówki (headers=)
  • Wysyłanie JSON (json=) i form data (data=)
  • Obsługa błędów: response.raise_for_status(), try/except
  • Session dla wydajności
  • Timeouty dla bezpieczeństwa

Checkpoint

Przed przejściem dalej:

  • Rozumiesz różnicę między GET a POST
  • Potrafisz wysłać zapytanie GET z parametrami
  • Potrafisz wysłać POST z JSON
  • Wiesz jak obsłużyć błędy HTTP
  • Rozumiesz kody statusu (2xx, 4xx, 5xx)

Analogia Safari: HTTP to system kurierski między stacjami badawczymi. GET = "wyślij mi dane", POST = "zapisz te nowe obserwacje", kody statusu = potwierdzenia dostawy!

W następnej lekcji Darwin pokaże Ci REST API - standardy architektury API, które pozwalają budować profesjonalne systemy wymiany danych!

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Która metoda HTTP służy do pobierania danych?

  2. 2. Jak wykonać request GET w bibliotece requests?

To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Układanie w poziomie

    Ułóż elementy w prawidłowej kolejności:

  • Klikanie w kolejności

    Kliknij elementy w prawidłowej kolejności:

  • Edytor kodu

    Pobierz dane z URL 'https://api.example.com/animals' używając requests

  • Edytor kodu

    Wykonaj GET i sparsuj odpowiedź jako JSON

  • Klikanie w kolejności

    Kliknij w kolejności: response = requests.get(url)

Przydatne artykuły