Python: работа с API (requests)
Научитесь использовать библиотеку requests в Python: GET и POST запросы, заголовки, параметры, JSON-ответы и обработка ошибок.
Библиотека requests — стандартный способ выполнять HTTP-запросы из Python. Она оборачивает низкоуровневый модуль urllib в удобный API, так что загрузка страницы или обращение к REST API занимает одну строку вместо десяти. В этой главе рассматривается всё необходимое: установка requests, GET и POST запросы, передача заголовков и параметров запроса, работа с JSON-ответами, загрузка файлов, использование сессий и надёжная обработка ошибок.
Установка
requests не входит в стандартную библиотеку, поэтому устанавливается через pip:
pip install requestsЕсли вы работаете внутри виртуального окружения (рекомендуется), сначала активируйте его, чтобы пакет был ограничен вашим проектом. После установки проверьте, что всё работает:
import requests
print(requests.__version__) # e.g. 2.32.3Выполнение GET-запроса
requests.get() отправляет HTTP GET-запрос и возвращает объект Response. Это наиболее распространённая операция — используется для получения данных из API, веб-страниц и файлов.
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
print(response.status_code) # 200
print(response.url) # https://jsonplaceholder.typicode.com/todos/1
print(response.text) # raw response body as a stringОжидаемый вывод:
200
https://jsonplaceholder.typicode.com/todos/1
{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}Чтение ответа как JSON
Большинство современных API возвращают JSON. Вызовите .json() на объекте ответа вместо ручного разбора .text — это вызывает json.loads() автоматически и возвращает словарь или список Python.
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
data = response.json()
print(data["title"]) # delectus aut autem
print(data["completed"]) # FalseПодробнее о том, как объекты Python соотносятся с типами JSON, читайте в главе Python JSON.
Передача параметров запроса
Параметры запроса — это пары ключ-значение после ? в URL, например ?q=python&page=2. Передайте их как словарь в аргумент params — requests автоматически закодирует их и добавит в URL.
import requests
params = {
"q": "python requests",
"page": 1,
"per_page": 5,
}
response = requests.get("https://httpbin.org/get", params=params)
# requests builds the full URL for you
print(response.url)
# https://httpbin.org/get?q=python+requests&page=1&per_page=5Всегда используйте params= вместо ручного построения URL — это правильно обрабатывает специальные символы и кодирование.
Передача заголовков запроса
Заголовки содержат метаданные: токены аутентификации, предпочтения типа содержимого, ключи API и многое другое. Передайте их как словарь в headers=:
import requests
headers = {
"Accept": "application/json",
"Authorization": "Bearer my-api-token",
"User-Agent": "MyApp/1.0",
}
response = requests.get("https://httpbin.org/headers", headers=headers)
print(response.json())Распространённые заголовки, которые вы будете отправлять:
| Заголовок | Назначение |
|---|---|
Authorization | Токен аутентификации (Bearer, Basic и др.) |
Content-Type | Формат тела запроса (например, application/json) |
Accept | Формат, который вы хотите получить от сервера |
User-Agent | Идентифицирует ваш клиент для сервера |
X-API-Key | Ключ API в пользовательском заголовке (зависит от сервиса) |
Выполнение POST-запроса
requests.post() отправляет данные на сервер — используется для создания ресурсов, отправки форм или вызова действий.
Отправка JSON
Передайте словарь Python в json=. Библиотека сериализует его и автоматически задаёт заголовок Content-Type: application/json:
import requests
payload = {
"title": "Buy groceries",
"completed": False,
"userId": 1,
}
response = requests.post(
"https://jsonplaceholder.typicode.com/todos",
json=payload,
)
print(response.status_code) # 201 Created
print(response.json())Ожидаемый вывод:
201
{'title': 'Buy groceries', 'completed': False, 'userId': 1, 'id': 201}Отправка данных формы
Некоторые API или HTML-формы ожидают данные в формате application/x-www-form-urlencoded. Используйте data= вместо json=:
import requests
form_data = {
"username": "alice",
"password": "secret",
}
response = requests.post("https://httpbin.org/post", data=form_data)
print(response.status_code)Отправка файлов (multipart-загрузка)
Чтобы загрузить файл, откройте его в бинарном режиме и передайте через files=:
import requests
with open("report.pdf", "rb") as f:
response = requests.post(
"https://httpbin.org/post",
files={"file": f},
)
print(response.status_code)requests кодирует загрузку как multipart/form-data, что ожидает большинство конечных точек для загрузки файлов.
Другие HTTP-методы
REST API используют разные HTTP-глаголы для разных операций. requests предоставляет одну функцию для каждого метода:
import requests
base = "https://jsonplaceholder.typicode.com/todos/1"
# Update a resource (replace entirely)
response = requests.put(base, json={"title": "Updated", "completed": True, "userId": 1})
print(response.status_code) # 200
# Partial update
response = requests.patch(base, json={"completed": True})
print(response.status_code) # 200
# Delete a resource
response = requests.delete(base)
print(response.status_code) # 200Обработка ошибок
Проверка кодов состояния
HTTP-код состояния сообщает, был ли запрос успешным. Наиболее важные группы:
| Диапазон | Значение |
|---|---|
| 2xx | Успех (200 OK, 201 Created, 204 No Content) |
| 3xx | Перенаправление (обрабатывается автоматически requests) |
| 4xx | Ошибка клиента (400 Bad Request, 401 Unauthorized, 404 Not Found) |
| 5xx | Ошибка сервера (500 Internal Server Error, 503 Service Unavailable) |
raise_for_status()
Вызов .raise_for_status() на ответе автоматически генерирует исключение HTTPError, если код состояния равен 4xx или 5xx. Это самый чистый способ быстро реагировать на неудачные ответы:
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/99999")
try:
response.raise_for_status()
data = response.json()
print(data)
except requests.exceptions.HTTPError as err:
print(f"HTTP error: {err}")Без raise_for_status() ответ 404 выглядит как успешный — ваш код читает тело ошибки и молча его обрабатывает.
Обработка сетевых ошибок
Сетевые сбои (ошибка DNS-поиска, отказ в подключении, тайм-аут) генерируют requests.exceptions.ConnectionError или requests.exceptions.Timeout. Перехватывайте оба с помощью базового requests.exceptions.RequestException:
import requests
try:
response = requests.get("https://api.example.com/data", timeout=5)
response.raise_for_status()
data = response.json()
except requests.exceptions.Timeout:
print("The request timed out — server took too long to respond.")
except requests.exceptions.ConnectionError:
print("Could not connect — check your network or the URL.")
except requests.exceptions.HTTPError as err:
print(f"HTTP error {response.status_code}: {err}")
except requests.exceptions.RequestException as err:
print(f"Unexpected error: {err}")Этот шаблон охватывает полную иерархию исключений: тайм-аут, подключение, HTTP-ошибка и базовый класс для всех остальных случаев. Подробнее об обработке исключений в Python читайте в главе Python try/except.
Всегда задавайте тайм-аут
По умолчанию requests будет ждать вечно, если сервер никогда не ответит. Всегда передавайте timeout=, чтобы избежать зависания программ:
# timeout=(connect_timeout, read_timeout) in seconds
response = requests.get("https://api.example.com/data", timeout=(3, 10))Форма с кортежем задаёт тайм-аут подключения и тайм-аут чтения отдельно. Тайм-аут чтения в 10 секунд означает «ждать до 10 секунд между байтами после установления соединения».
Использование сессий
Объект requests.Session сохраняет настройки — заголовки, куки, аутентификацию — для нескольких запросов к одному хосту. Он также повторно использует базовое TCP-соединение (пул соединений), что быстрее, чем создание нового соединения для каждого вызова.
import requests
with requests.Session() as session:
# Set headers once — every request in this session will include them
session.headers.update({
"Authorization": "Bearer my-api-token",
"Accept": "application/json",
})
# All requests reuse the connection and headers
r1 = session.get("https://api.example.com/users")
r2 = session.get("https://api.example.com/posts")
r3 = session.post("https://api.example.com/todos", json={"title": "New"})
print(r1.status_code, r2.status_code, r3.status_code)Используйте сессию всякий раз, когда делаете более одного запроса к одному серверу.
Анализ ответа
Объект Response предоставляет всё, что сервер отправил обратно:
import requests
response = requests.get("https://httpbin.org/get")
print(response.status_code) # 200
print(response.reason) # OK
print(response.headers) # dict of response headers
print(response.headers["Content-Type"]) # application/json
print(response.encoding) # utf-8
print(response.elapsed) # how long the request took
print(response.url) # final URL (after redirects)Для бинарного содержимого (изображения, PDF) используйте response.content (возвращает bytes) вместо response.text:
import requests
response = requests.get("https://httpbin.org/image/png")
with open("image.png", "wb") as f:
f.write(response.content)Аутентификация
HTTP Basic Auth
Передайте кортеж (username, password) в auth=:
import requests
response = requests.get(
"https://httpbin.org/basic-auth/alice/secret",
auth=("alice", "secret"),
)
print(response.status_code) # 200Bearer-токен (ключи API)
Большинство современных API используют bearer-токен в заголовке Authorization:
import requests
headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}
response = requests.get("https://api.example.com/me", headers=headers)Никогда не вписывайте токены в исходные файлы. Загружайте их из переменных окружения или менеджера секретов:
import os
import requests
token = os.environ["API_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
response = requests.get("https://api.example.com/me", headers=headers)Реальный пример — GitHub API
Этот пример демонстрирует полный шаблон: сессия, bearer-аутентификация, пагинация, обработка ошибок и разбор JSON:
import os
import requests
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
BASE_URL = "https://api.github.com"
with requests.Session() as session:
session.headers.update({
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
})
try:
# Fetch the first page of public repos for a user
response = session.get(
f"{BASE_URL}/users/torvalds/repos",
params={"per_page": 5, "sort": "updated"},
timeout=10,
)
response.raise_for_status()
repos = response.json()
for repo in repos:
print(f"{repo['name']:40s} ★ {repo['stargazers_count']}")
except requests.exceptions.HTTPError as err:
print(f"GitHub API error: {err}")
except requests.exceptions.RequestException as err:
print(f"Network error: {err}")Этот шаблон — сессия с общими заголовками, raise_for_status(), ограниченный try/except — является production-готовым подходом для любого API-клиента.
Параллельные запросы с asyncio
Для программ, которые обращаются ко многим конечным точкам одновременно, синхронная библиотека requests блокируется на каждом вызове. Перейдите на aiohttp (асинхронный аналог) и совместите его с модулем Python asyncio:
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.json()
async def main():
urls = [
"https://jsonplaceholder.typicode.com/todos/1",
"https://jsonplaceholder.typicode.com/todos/2",
"https://jsonplaceholder.typicode.com/todos/3",
]
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(*[fetch(session, u) for u in urls])
for r in results:
print(r["title"])
asyncio.run(main())Прочитайте главу Python asyncio, чтобы понять, как работают async/await, прежде чем применять этот шаблон.
Краткий справочник
| Задача | Код |
|---|---|
| GET-запрос | requests.get(url) |
| GET с параметрами | requests.get(url, params={"key": "val"}) |
| GET с заголовками | requests.get(url, headers={"Authorization": "Bearer token"}) |
| POST с JSON-телом | requests.post(url, json={"key": "val"}) |
| POST с данными формы | requests.post(url, data={"key": "val"}) |
| Загрузка файла | requests.post(url, files={"file": open("f.pdf", "rb")}) |
| PUT / PATCH / DELETE | requests.put/patch/delete(url, json=...) |
| Проверка статуса | response.status_code |
| Исключение при 4xx/5xx | response.raise_for_status() |
| Разбор JSON-тела | response.json() |
| Тело как текст | response.text |
| Тело как байты | response.content |
| Задать тайм-аут | requests.get(url, timeout=5) |
| Повторное использование соединения | with requests.Session() as s: ... |
Связанные главы
- Python pip — установка
requestsи управление зависимостями проекта - Python JSON — понимание JSON-кодирования, лежащего в основе большинства API-ответов
- Python try/except — написание надёжной обработки исключений для сетевых вызовов
- Python Virtual Environments — изоляция зависимостей проекта
- Python asyncio — параллельные HTTP-запросы без блокировки