W3docs

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. Передайте их как словарь в аргумент paramsrequests автоматически закодирует их и добавит в 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)   # 200

Bearer-токен (ключи 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 / DELETErequests.put/patch/delete(url, json=...)
Проверка статусаresponse.status_code
Исключение при 4xx/5xxresponse.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-запросы без блокировки

Практика

Практика
Какой аргумент отправляет словарь Python как JSON-тело в POST-запросе?
Какой аргумент отправляет словарь Python как JSON-тело в POST-запросе?
Was this page helpful?