Тянем данные из интернета в пару строк
Большинству реальных Python-программ рано или поздно нужно поговорить с чем-то по сети — REST API, сервисом погоды, эндпойнтом GitHub, скачать файл. Стандартная библиотека это умеет (через urllib), но де-факто инструмент мира Python — сторонняя библиотека requests. Её API настолько приятнее, что один pip install того стоит.
pip install requests
Если ты ещё не работаешь в виртуальном окружении — сначала создай его: это удержит установку в пределах проекта.
Первый GET-запрос
Три строки: вызов get, проверка статус-кода, взгляд на тело. response.text — тело ответа строкой. Здесь обрезано: полный JSON длинный.
Статус-коды, которые стоит узнавать в лицо:
- 200 — OK, всё сработало.
- 201 — Created (типичный ответ на POST).
- 301 / 302 — редиректы;
requestsпо умолчанию автоматически их следует. - 400 — плохой запрос; что-то в отправленном не так.
- 401 / 403 — не аутентифицирован / нет прав.
- 404 — ресурс не существует.
- 429 — rate limit; замедлись.
- 500 — ошибка сервера.
Парсинг JSON-ответа
Когда эндпойнт возвращает JSON — вызывай .json() на ответе: он распарсит тело и отдаст тебе словарь (или список):
Под капотом .json() — то же самое, что json.loads(response.text), просто сокращение для типичного случая.
Query-параметры
Не склеивай ?key=value&... к URL руками. Передавай словарь в params=:
requests сам кодирует URL — пробелы, спецсимволы, Unicode всё переживают корректно.
Реально ушедший URL доступен в response.url — удобно для отладки.
POST с JSON-телом
Для отправки данных — создание ресурсов, формы, мутирующие эндпойнты — используй requests.post:
import requests
payload = {
"title": "Docs update",
"body": "Added HTTP requests page.",
"labels": ["docs"],
}
response = requests.post(
"https://api.example.com/issues",
json=payload,
headers={"Authorization": "Bearer YOUR_TOKEN"},
)
print(response.status_code)
print(response.json())
Аргумент json= делает две вещи: сериализует payload в JSON и проставляет Content-Type: application/json. Можно сделать то же самое руками через data=json.dumps(payload) и явный заголовок, но json= — идиоматичное сокращение.
Для form-encoded данных (то, что шлёт классическая HTML-форма) используй data=:
requests.post("https://example.com/login", data={"user": "rosa", "password": "..."})
Заголовки
Любые свои заголовки — словарём:
import requests
response = requests.get(
"https://api.example.com/profile",
headers={
"Authorization": "Bearer abc123",
"User-Agent": "my-tool/1.0",
},
)
Большинство API хотят заголовок Authorization для аутентификации. Конкретная схема (Bearer, Basic, Token) — в их документации.
Таймаут не опционален
По умолчанию requests будет ждать ответа вечно. В реальной программе это превращает флейкующий сервер в подвисший скрипт. Всегда передавай timeout:
import requests
try:
response = requests.get("https://api.example.com/slow", timeout=5)
except requests.Timeout:
print("Server took too long.")
Число — секунды. timeout=5 — «сдавайся, если за 5 секунд ответа нет». Можно передать кортеж (connect_timeout, read_timeout) для большего контроля.
Обработка ошибок
Бывают две категории проблем:
- Ошибки уровня HTTP (4xx, 5xx) — сервер ответил, но ответ — ошибка.
response.status_codeскажет. - Сетевые проблемы — таймауты, DNS, недоступные хосты. Они поднимают исключения.
Идиоматичный паттерн сочетает оба:
raise_for_status() для 2xx-ответов — no-op, для остальных — поднимает исключение. RequestException — базовый класс для каждой ошибки, поднимаемой requests: единая ловушка для «что-то пошло не так при разговоре с эндпойнтом».
Скачиваем файл
Для больших бинарных загрузок — стримь ответ, чтобы он не сидел в памяти:
import requests
url = "https://example.com/large.zip"
with requests.get(url, stream=True, timeout=30) as r:
r.raise_for_status()
with open("large.zip", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
stream=True говорит requests не предзагружать тело. iter_content(chunk_size=...) отдаёт тело кусками, и ты пишешь их сразу на диск.
Сессии: переиспользование соединений и дефолтов
Если собираешься делать несколько запросов к одному сервису, используй Session. Он переиспользует нижележащее TCP-соединение (быстрее) и позволяет один раз задать дефолты:
import requests
session = requests.Session()
session.headers.update({"Authorization": "Bearer abc123"})
# Every request through this session carries the header.
a = session.get("https://api.example.com/users/1")
b = session.get("https://api.example.com/users/2")
c = session.post("https://api.example.com/users", json={"name": "Rosa"})
Для скриптов, бьющих в один и тот же API десятки раз, сессия даёт заметный прирост.
Реалистичный пример: маленький GitHub-клиент
Получаем последний релиз репозитория:
import requests
def latest_release(owner, repo):
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
return {
"tag": data["tag_name"],
"name": data["name"],
"published": data["published_at"],
"url": data["html_url"],
}
release = latest_release("python", "cpython")
print(release)
Меньше пятнадцати строк: построил URL, запросил, проверил на ошибки, достал интересующие поля. Такую форму принимают большинство API-клиентов, которые ты будешь писать.
А что насчёт urllib?
urllib.request из стандартной библиотеки умеет всё, что requests, — в большем числе строк и с худшей эргономикой. Если добавить зависимость никак нельзя — вот:
import json
import urllib.request
with urllib.request.urlopen("https://api.github.com/repos/python/cpython") as r:
data = json.loads(r.read().decode("utf-8"))
print(data["name"])
Для чего-то посерьёзнее быстрого скрипта requests (или httpx, если нужно async) стоит установки.
Несколько привычек
- Всегда ставь таймаут. Без исключений.
- Используй
raise_for_status()в скриптах, ожидающих успеха, — превратит плохой ответ в громкое исключение. - Передавай словари в
params=иjson=, а не склеивай строки руками. - Заворачивай связанные вызовы в
Session, когда много раз ходишь в один API. - Логируй
response.status_codeиresponse.textпри отладке — тело обычно прямо говорит, что сервер не так понял.
Дальше: даты и время
С requests в арсенале ты умеешь говорить с любым современным веб-API, качать файлы и строить маленькие интеграции между сервисами. В связке со страницами про JSON и CSV у тебя теперь полный цикл «забрать данные, прочесть, что-то с ними сделать, записать обратно» — форма огромного числа реальных Python-скриптов. У большинства этих данных есть временная метка, и следующая страница — о том, как Python представляет даты, время и каких часовых ловушек стоит избегать.
Часто задаваемые вопросы
Как сделать HTTP-запрос в Python?
Установи библиотеку requests через pip install requests, затем вызови requests.get(url) для GET или requests.post(url, json=...) для POST. У объекта ответа есть .status_code, .text, .json() и .headers. Пример: r = requests.get('https://api.example.com/users/1').
requests или urllib?
requests — для всего, что ты пишешь руками: его API значительно дружелюбнее. urllib встроен и нормально, когда добавить зависимость нельзя, но на JSON-тела и сессии нужно больше кода. В продакшене многие команды используют ещё httpx — requests-совместимую библиотеку с async-поддержкой.
Как послать JSON в POST-запросе?
Передай json={'key': 'value'} в requests.post(...). requests сам сериализует словарь в JSON и проставит Content-Type: application/json. Не передавай одновременно data= и json= — выбери одно.
Как обрабатывать ошибки с requests?
Проверь response.status_code (200 — успех) или вызови response.raise_for_status(), чтобы поднять исключение на 4xx/5xx. Сетевые проблемы (таймауты, падения DNS) поднимают наследников requests.RequestException — лови его, чтобы покрыть всё.