Библиотека requests: HTTP-запросы в Python
Библиотека requests в Python: установка, GET и POST запросы, params, JSON, заголовки, таймауты, сессии, загрузка файлов, обработка ошибок и кодировки.
Содержание · 14 разделов
- Установка requests
- GET-запрос и параметры строки запроса
- Что есть в ответе: status_code, json(), text
- POST-запрос: json= и data=
- Заголовки и авторизация
- Таймауты: всегда указывайте timeout
- Обработка ошибок: raise_for_status и исключения
- Сессии: Session для серии запросов
- Отправка файлов
- Скачивание файлов
- Кодировки: кракозябры в r.text
- Частые ошибки
- Частые вопросы
- Как передать cookies и прочитать их из ответа?
- Почему сайт отвечает 403, хотя в браузере открывается?
- Как отправить запрос через прокси?
- Как посмотреть, какой запрос ушёл на сервер?
- Можно ли отправлять запросы requests асинхронно?
- Итог
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.ok | True, если код меньше 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:
| Исключение | Когда возникает |
|---|---|
HTTPError | raise_for_status() при статусе 4xx/5xx |
ConnectionError | DNS не нашёл хост, соединение отклонено, нет сети |
Timeout | истёк timeout (ConnectTimeout, ReadTimeout) |
TooManyRedirects | бесконечная цепочка редиректов |
MissingSchema | в URL нет https:// |
JSONDecodeError | r.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