EasyТеория4 min

HTTP-запросы

urllib, requests, httpx: GET/POST-запросы, аутентификация и таймауты

HTTP-запросы в Python

urllib — стандартная библиотека

Модуль urllib входит в стандартную библиотеку и не требует установки:

import urllib.request
import urllib.parse
import json

# Simple GET request
url = "https://jsonplaceholder.typicode.com/posts/1"
with urllib.request.urlopen(url) as response:
    data = json.loads(response.read().decode())
    print(f"Статус: {response.status}")
    print(f"Заголовок: {data['title']}")

# GET with query parameters
params = urllib.parse.urlencode({"userId": 1})
url = f"https://jsonplaceholder.typicode.com/todos?{params}"
with urllib.request.urlopen(url) as response:
    todos = json.loads(response.read().decode())
    print(f"Найдено: {len(todos)} задач")

# Error handling
from urllib.error import HTTPError, URLError

try:
    with urllib.request.urlopen("https://httpbin.org/status/404") as resp:
        pass
except HTTPError as e:
    print(f"HTTP ошибка: {e.code} {e.reason}")
except URLError as e:
    print(f"Ошибка соединения: {e.reason}")

urllib работает, но его API громоздкий. Для реальных проектов используйте requests или httpx.

requests — удобный HTTP-клиент

pip install requests
import requests

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

# GET with parameters
response = requests.get(
    "https://jsonplaceholder.typicode.com/posts",
    params={"userId": 1},
)
posts = response.json()
print(f"Постов: {len(posts)}")

# POST request
response = requests.post(
    "https://jsonplaceholder.typicode.com/posts",
    json={"title": "Новый пост", "body": "Содержимое", "userId": 1},
)
print(f"Создан: #{response.json()['id']}")

# Custom headers
response = requests.get(
    "https://api.github.com/user",
    headers={"Authorization": "Bearer ghp_token_here"},
)

Таймауты и повторные запросы

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

# Always set timeout!
try:
    response = requests.get("https://httpbin.org/delay/5", timeout=3)
except requests.Timeout:
    print("Запрос превысил таймаут")
except requests.ConnectionError:
    print("Ошибка соединения")

# Tuple timeout: (connect_timeout, read_timeout)
response = requests.get("https://httpbin.org/get", timeout=(3, 10))

# Session with retries
session = requests.Session()
retries = Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503])
session.mount("https://", HTTPAdapter(max_retries=retries))
response = session.get("https://httpbin.org/get", timeout=5)

Аутентификация и сессии

import requests

# Basic Auth
response = requests.get(
    "https://httpbin.org/basic-auth/user/pass",
    auth=("user", "pass"),
)

# Bearer Token
response = requests.get(
    "https://api.example.com/data",
    headers={"Authorization": "Bearer your_token"},
)

# Session — cookies persist between requests
session = requests.Session()
session.headers.update({"User-Agent": "MyApp/1.0"})
session.post("https://httpbin.org/cookies/set/session_id/abc123")
response = session.get("https://httpbin.org/cookies")
print(response.json())
session.close()

httpx — современная альтернатива

httpx совместим с requests по API, но поддерживает async/await и HTTP/2:

pip install httpx
import httpx

# Synchronous API (like requests)
response = httpx.get("https://jsonplaceholder.typicode.com/posts/1")
print(response.json()["title"])

# With client (connection pooling)
with httpx.Client(
    base_url="https://jsonplaceholder.typicode.com",
    timeout=10.0,
) as client:
    posts = client.get("/posts", params={"userId": 1}).json()
    user = client.get("/users/1").json()
    print(f"{user['name']}: {len(posts)} постов")

Асинхронные запросы

import asyncio
import httpx

async def fetch_data() -> None:
    async with httpx.AsyncClient(
        base_url="https://jsonplaceholder.typicode.com",
        timeout=10.0,
    ) as client:
        # Concurrent requests
        responses = await asyncio.gather(
            client.get("/users/1"),
            client.get("/posts", params={"userId": 1}),
            client.get("/comments", params={"postId": 1}),
        )

        user = responses[0].json()
        posts = responses[1].json()
        comments = responses[2].json()

        print(f"Пользователь: {user['name']}")
        print(f"Постов: {len(posts)}, комментариев: {len(comments)}")

asyncio.run(fetch_data())

Обработка ошибок

import httpx

def safe_api_call(url: str) -> dict | None:
    """Make API call with comprehensive error handling."""
    try:
        response = httpx.get(url, timeout=5.0)
        response.raise_for_status()
        return response.json()
    except httpx.TimeoutException:
        print(f"Таймаут при запросе к {url}")
    except httpx.HTTPStatusError as e:
        print(f"HTTP ошибка {e.response.status_code}")
    except httpx.ConnectError:
        print(f"Не удалось подключиться к {url}")
    except httpx.RequestError as e:
        print(f"Ошибка запроса: {e}")
    return None

Практический пример: API-клиент

from dataclasses import dataclass
import httpx

@dataclass
class APIClient:
    """Reusable API client with error handling."""
    base_url: str
    token: str | None = None
    timeout: float = 10.0

    def _headers(self) -> dict[str, str]:
        headers = {"Accept": "application/json"}
        if self.token:
            headers["Authorization"] = f"Bearer {self.token}"
        return headers

    def get(self, path: str, params: dict | None = None) -> dict:
        response = httpx.get(
            f"{self.base_url}{path}",
            params=params,
            headers=self._headers(),
            timeout=self.timeout,
        )
        response.raise_for_status()
        return response.json()

    def post(self, path: str, data: dict) -> dict:
        response = httpx.post(
            f"{self.base_url}{path}",
            json=data,
            headers=self._headers(),
            timeout=self.timeout,
        )
        response.raise_for_status()
        return response.json()

# Usage
client = APIClient(base_url="https://jsonplaceholder.typicode.com")
posts = client.get("/posts", params={"userId": 1})
new_post = client.post("/posts", data={"title": "Новый", "body": "Текст", "userId": 1})
print(f"Получено {len(posts)} постов, создан #{new_post['id']}")

Проверь себя

Какой модуль для HTTP-запросов входит в стандартную библиотеку Python?

Что произойдёт, если не указать timeout в requests.get()?

Чем httpx отличается от requests?

Зачем использовать `response.raise_for_status()` после запроса?