Django-Admin.ru

Библиотека requests: HTTP-запросы в Python

Библиотека requests в Python: установка, GET и POST запросы, params, JSON, заголовки, таймауты, сессии, загрузка файлов, обработка ошибок и кодировки.

Редакция Django-Admin.ru
· обновлено · 8 мин чтения

Содержание · 14 разделов
  1. Установка requests
  2. GET-запрос и параметры строки запроса
  3. Что есть в ответе: status_code, json(), text
  4. POST-запрос: json= и data=
  5. Заголовки и авторизация
  6. Таймауты: всегда указывайте timeout
  7. Обработка ошибок: raise_for_status и исключения
  8. Сессии: Session для серии запросов
  9. Отправка файлов
  10. Скачивание файлов
  11. Кодировки: кракозябры в r.text
  12. Частые ошибки
  13. Частые вопросы
  14. Как передать cookies и прочитать их из ответа?
  15. Почему сайт отвечает 403, хотя в браузере открывается?
  16. Как отправить запрос через прокси?
  17. Как посмотреть, какой запрос ушёл на сервер?
  18. Можно ли отправлять запросы requests асинхронно?
  19. Итог

requests — самая популярная библиотека Python для HTTP-запросов: с её помощью скрипт получает данные из API, отправляет формы и скачивает файлы. Минимальный пример с Python requests:

import requests

response = requests.get("https://httpbin.org/get", params={"q": "django"}, timeout=10)
response.raise_for_status()   # исключение, если статус 4xx или 5xx
print(response.json())        # тело ответа как словарь

В статье — установка, GET и POST, заголовки, таймауты, сессии, отправка и скачивание файлов, обработка ошибок и кодировки. Все примеры используют тестовый сервис httpbin.org, который возвращает обратно то, что вы ему отправили, и проверены на requests 2.34 и Python 3.12.

Установка requests

Библиотека не входит в стандартную библиотеку Python, поэтому её ставят через pip — лучше в виртуальном окружении проекта:

# Windows
py -m pip install requests

# macOS / Linux
python3 -m pip install requests

Проверка:

import requests

print(requests.__version__)  # → 2.34.2

Если при импорте появляется ModuleNotFoundError: No module named 'requests', пакет установлен в другой интерпретатор — разбор этой ошибки есть в статье про ModuleNotFoundError. Подробнее о pip — в руководстве по pip.

GET-запрос и параметры строки запроса

GET-запрос получает данные. Параметры строки запроса (всё, что после ? в URL) передавайте словарём params — requests сам закодирует кириллицу и спецсимволы:

import requests

r = requests.get(
    "https://httpbin.org/get",
    params={"q": "django", "page": 2},
    timeout=10,
)
print(r.url)  # → https://httpbin.org/get?q=django&page=2

Если значение — список, параметр повторится: params={"tags": ["a", "b"]} даст ?tags=a&tags=b.

Что есть в ответе: status_code, json(), text

requests.get() возвращает объект Response. Его главные атрибуты:

Атрибут / методЧто содержит
r.status_codeкод ответа: 200, 404, 500…
r.okTrue, если код меньше 400
r.json()тело, разобранное из JSON в словарь или список
r.textтело как строка (str)
r.contentтело как байты (bytes) — для картинок, PDF, архивов
r.headersзаголовки ответа (регистр ключей не важен)
r.encodingкодировка, которой декодируется r.text
r.urlитоговый URL (после редиректов)
r.historyсписок промежуточных ответов при редиректах
r.elapsedсколько занял запрос (timedelta)
r = requests.get("https://httpbin.org/get", params={"q": "django"}, timeout=10)
print(r.status_code)              # → 200
print(r.headers["Content-Type"])  # → application/json
data = r.json()
print(data["args"])               # → {'q': 'django'}

r.json() вызывайте только для ответов в формате JSON. Если сервер вернул HTML или пустое тело, будет исключение requests.exceptions.JSONDecodeError.

POST-запрос: json= и data=

POST отправляет данные на сервер. Для API с JSON используйте параметр json= — requests сериализует словарь и поставит заголовок Content-Type: application/json:

r = requests.post(
    "https://httpbin.org/post",
    json={"name": "Анна", "age": 28},
    timeout=10,
)
print(r.json()["json"])  # → {'age': 28, 'name': 'Анна'}

Для HTML-форм (application/x-www-form-urlencoded) — параметр data=:

r = requests.post("https://httpbin.org/post", data={"login": "anna"}, timeout=10)
print(r.json()["form"])  # → {'login': 'anna'}

Частая ошибка — передать словарь в data=, когда API ждёт JSON: сервер получит форму и ответит ошибкой 400 или 415. Остальные HTTP-методы вызываются так же: requests.put(), requests.patch(), requests.delete().

Заголовки и авторизация

headers = {
    "User-Agent": "my-app/1.0",
    "Authorization": "Bearer ВАШ_ТОКЕН",
}
r = requests.get("https://httpbin.org/headers", headers=headers, timeout=10)

Для HTTP Basic-авторизации есть параметр auth:

r = requests.get("https://httpbin.org/basic-auth/user/pass", auth=("user", "pass"), timeout=10)
print(r.status_code)  # → 200

Важно: не храните токены в коде. Читайте их из переменных окружения: os.environ["API_TOKEN"].

Таймауты: всегда указывайте timeout

По умолчанию requests не ограничивает время ожидания: если сервер не отвечает, программа может зависнуть навсегда. Поэтому timeout нужен в каждом запросе:

requests.get("https://httpbin.org/get", timeout=10)         # 10 секунд на подключение и на ожидание данных
requests.get("https://httpbin.org/get", timeout=(3.05, 27))  # (подключение, чтение) отдельно

timeout ограничивает ожидание подключения и паузы между порциями данных от сервера; общую длительность скачивания он не ограничивает. При превышении возникает requests.exceptions.Timeout (точнее, его подклассы ConnectTimeout или ReadTimeout).

Обработка ошибок: raise_for_status и исключения

Ответ 404 или 500 сам по себе не вызывает исключения — requests.get() вернёт объект с соответствующим status_code. Чтобы превратить такой ответ в исключение, вызовите raise_for_status():

r = requests.get("https://httpbin.org/status/404", timeout=10)
print(r.status_code, r.ok)  # → 404 False
r.raise_for_status()
# requests.exceptions.HTTPError: 404 Client Error: NOT FOUND for url: https://httpbin.org/status/404

Все исключения библиотеки лежат в requests.exceptions и наследуются от RequestException:

ИсключениеКогда возникает
HTTPErrorraise_for_status() при статусе 4xx/5xx
ConnectionErrorDNS не нашёл хост, соединение отклонено, нет сети
Timeoutистёк timeout (ConnectTimeout, ReadTimeout)
TooManyRedirectsбесконечная цепочка редиректов
MissingSchemaв URL нет https://
JSONDecodeErrorr.json() для ответа не в формате JSON
RequestExceptionбазовый класс — ловит всё перечисленное

Типовой шаблон с try/except:

import requests


def fetch_json(url: str) -> dict | None:
    try:
        r = requests.get(url, timeout=10)
        r.raise_for_status()
        return r.json()
    except requests.exceptions.Timeout:
        print("Сервер не ответил вовремя")
    except requests.exceptions.HTTPError as e:
        print(f"Ошибка HTTP: {e.response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"Запрос не удался: {e}")
    return None

Порядок except важен: сначала конкретные исключения, в конце — базовый RequestException.

Сессии: Session для серии запросов

Если вы делаете много запросов к одному сайту, используйте requests.Session. Сессия переиспользует TCP-соединение (это быстрее), хранит cookies между запросами и общие заголовки:

with requests.Session() as s:
    s.headers.update({"User-Agent": "my-app/1.0"})
    s.get("https://httpbin.org/cookies/set/sessionid/123", timeout=10)
    r = s.get("https://httpbin.org/cookies", timeout=10)
    print(r.json())  # → {'cookies': {'sessionid': '123'}}

Через сессию также настраиваются повторные попытки при временных сбоях:

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

retry = Retry(total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504])
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))
r = session.get("https://httpbin.org/get", timeout=10)

timeout в сессии всё равно передаётся в каждый запрос — общего таймаута у Session нет.

Отправка файлов

Файл открывайте в бинарном режиме "rb" и передавайте в параметр files:

with open("report.txt", "rb") as f:
    r = requests.post(
        "https://httpbin.org/post",
        files={"file": ("report.txt", f, "text/plain")},
        data={"comment": "Отчёт за январь"},
        timeout=10,
    )

Кортеж задаёт имя файла, сам файл и MIME-тип; достаточно и короткой формы files={"file": f}. Запрос уйдёт как multipart/form-data.

Скачивание файлов

Небольшой файл можно сохранить из r.content. Для больших файлов используйте stream=True и запись частями — так файл не загружается в память целиком:

url = "https://httpbin.org/image/png"
with requests.get(url, stream=True, timeout=10) as r:
    r.raise_for_status()
    with open("image.png", "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)

Кодировки: кракозябры в r.text

r.text декодирует байты кодировкой из r.encoding, а её requests берёт из заголовка Content-Type. Если сервер прислал text/html без charset, requests по стандарту HTTP подставит ISO-8859-1, и кириллица превратится в нечитаемые символы. Исправление — задать кодировку вручную до обращения к r.text:

r = requests.get(url, timeout=10)
print(r.encoding, r.apparent_encoding)  # из заголовка и угаданная по содержимому

r.encoding = r.apparent_encoding   # или явно: r.encoding = "utf-8" / "cp1251"
html = r.text

r.json() и r.content от этой настройки не зависят: байты всегда остаются исходными.

Частые ошибки

  • Нет timeout — скрипт зависает на «мёртвом» сервере. Указывайте его в каждом вызове.
  • Ожидание исключения на 404 — его не будет без raise_for_status() или проверки r.status_code.
  • data= вместо json= — API получает форму вместо JSON.
  • MissingSchema: Invalid URL 'httpbin.org/get': No scheme supplied — забыли https:// в адресе.
  • SSLError на корпоративной сети — обычно мешает прокси с подменой сертификата. Укажите путь к корпоративному сертификату в verify="путь/к/ca.pem"; отключать проверку через verify=False в рабочем коде небезопасно.
  • Склейка URL вручную — f"{url}?q={query}" ломается на пробелах и &. Передавайте параметры через params.

Частые вопросы

Как передать cookies и прочитать их из ответа?

Cookies для одного запроса передают словарём, а полученные от сервера читают из r.cookies:

r = requests.get("https://httpbin.org/cookies", cookies={"sessionid": "123"}, timeout=10)
print(r.json())  # → {'cookies': {'sessionid': '123'}}

r = requests.get("https://httpbin.org/cookies/set/sessionid/123", allow_redirects=False, timeout=10)
print(r.cookies.get_dict())  # → {'sessionid': '123'}

Если cookies нужно сохранять между запросами (например, после входа на сайт), используйте Session — она делает это автоматически.

Почему сайт отвечает 403, хотя в браузере открывается?

Сервер отличает скрипт от браузера. По умолчанию requests представляется как python-requests/2.34.2, и такие запросы часто блокируют. Передайте заголовок User-Agent как у браузера, а при необходимости — Accept-Language и Referer. Если сайт защищён проверкой JavaScript или капчей, заголовки не помогут: данные придётся получать через официальный API или браузер под управлением Selenium.

Как отправить запрос через прокси?

Адреса прокси передают словарём proxies — отдельно для HTTP и HTTPS:

proxies = {
    "http": "http://user:password@10.10.1.10:3128",
    "https": "http://user:password@10.10.1.10:3128",
}
r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)

Для SOCKS-прокси установите дополнительную зависимость pip install "requests[socks]" и укажите схему socks5://. Ещё requests читает прокси из переменных окружения HTTP_PROXY и HTTPS_PROXY.

Как посмотреть, какой запрос ушёл на сервер?

Отправленный запрос хранится в r.request — там метод, URL, заголовки и тело. Это помогает понять, почему API отвечает ошибкой:

r = requests.post("https://httpbin.org/post", json={"name": "Анна"}, timeout=10)
print(r.request.method, r.request.url)   # → POST https://httpbin.org/post
print(r.request.headers["Content-Type"])  # → application/json
print(r.request.body)                     # → b'{"name": "\\u0410\\u043d\\u043d\\u0430"}'

Можно ли отправлять запросы requests асинхронно?

Нет, requests работает синхронно: каждый вызов блокирует поток до получения ответа. Для асинхронного кода с async/await берут httpx (API похож на requests) или aiohttp. Если нужно просто ускорить сотню независимых запросов в обычном скрипте, хватит пула потоков concurrent.futures.ThreadPoolExecutor — в каждом потоке используйте свою Session.

Итог

Для большинства задач хватает requests.get() и requests.post() с params, json, headers и обязательным timeout, плюс raise_for_status() и обработки RequestException. Полученный JSON удобно разобрать в таблицу через pandas, а если вам нужен не клиент, а собственный API — посмотрите руководство по FastAPI. Другие популярные пакеты собраны в обзоре библиотек Python; полный справочник — в документации requests.

  • #Python
  • #requests
  • #HTTP
  • #API
  • #JSON
↑ ↓ выборEnter открытьEsc закрыть