Библиотека requests в Python: запросы к API с нуля
Коротко. requests — самая популярная библиотека Python для отправки HTTP-запросов. Она не входит в стандартную поставку, ставится командой pip install requests. Базовый сценарий такой: вызываете requests.get(url), получаете объект ответа, проверяете код состояния и разбираете тело методом .json(). Два правила, которые отличают рабочий код от учебного: всегда указывайте timeout и всегда обрабатывайте ошибки.
Что такое API и зачем к нему обращаться
Почти любой полезный сервис умеет отдавать данные не только человеку в браузере, но и другой программе. Прогноз погоды, курс валют, список товаров, отправка сообщения в Telegram — за всем этим стоит API , набор правил, по которым внешняя программа может запросить данные и получить ответ.
Общение почти всегда идет по протоколу HTTP по схеме «запрос — ответ». Программа отправляет запрос с адресом и параметрами, сервер возвращает ответ с данными и кодом состояния.
У HTTP есть несколько основных методов, и они соответствуют действиям:
- GET — получить данные
- POST — отправить данные, создать запись
- PUT и PATCH — обновить существующую запись целиком или частично
- DELETE — удалить
В библиотеке requests функции называются так же, и это делает код почти читаемым вслух.
Установка
Библиотека не входит в стандартную поставку Python, поэтому ее нужно установить:
pip install requests
Если работаете в виртуальном окружении — активируйте его перед установкой. Если pip не найден, попробуйте python -m pip install requests.
Проверить, что все встало:
import requests
print(requests.__version__)
Первый GET-запрос
Возьмем публичный API GitHub — он не требует ключа и удобен для проб.
import requests
response = requests.get("https://api.github.com/users/octocat")
print(response.status_code) # 200
data = response.json()
print(data["name"]) # The Octocat
print(data["public_repos"]) # число репозиториев
Три строки — и в программе появились живые данные с чужого сервера. Разберем, что происходит внутри.
Объект Response: что в нем есть
Функция get() возвращает не сами данные, а объект ответа. У него много полезных атрибутов:
print(response.status_code) # код состояния, например 200
print(response.ok) # True, если код меньше 400
print(response.headers) # заголовки ответа
print(response.url) # итоговый адрес запроса
print(response.encoding) # кодировка
print(response.elapsed) # сколько времени занял запрос
Атрибут response.url особенно полезен при отладке: он показывает адрес, который в итоге ушел на сервер, вместе со всеми подставленными параметрами.
Три способа получить тело ответа
Начинающие часто путают эти варианты, а разница принципиальная.
response.text — тело как строка. Подходит для HTML-страниц и текстовых ответов.
response.content — тело как последовательность байтов. Нужен для картинок, архивов, PDF и любых бинарных данных.
response.json() — разбирает JSON и возвращает словарь или список Python. Основной способ работы с API.
response = requests.get("https://api.github.com/users/octocat")
print(type(response.text)) # <class 'str'>
print(type(response.content)) # <class 'bytes'>
print(type(response.json())) # <class 'dict'>
Важная деталь: .json() сработает только если сервер действительно вернул JSON. Если в ответе оказалась HTML-страница с ошибкой, метод упадет с ошибкой декодирования. Поэтому в реальном коде его вызывают после проверки кода состояния, а не сразу.
Параметры запроса
Многие API принимают параметры прямо в адресе — после знака вопроса. Собирать такую строку вручную неудобно и опасно: пробелы и кириллицу нужно кодировать. Библиотека делает это сама, если передать словарь в аргумент params:
params = {
"q": "python",
"sort": "stars",
"order": "desc"
}
response = requests.get(
"https://api.github.com/search/repositories",
params=params
)
for repo in response.json()["items"][:5]:
print(repo["name"], repo["stargazers_count"])
Библиотека сама соберет корректный адрес и экранирует специальные символы. Проверить результат можно через response.url.
Коды состояния и проверка ответа
Код состояния — первое, что стоит смотреть в ответе. Коды делятся на группы:
- 2xx — успех. 200 — все хорошо, 201 — ресурс создан, 204 — успех без содержимого
- 3xx — перенаправление, библиотека обрабатывает его сама
- 4xx — ошибка на стороне клиента, то есть в вашем запросе
- 5xx — ошибка на стороне сервера
Из четырехсотых чаще всего встречаются четыре, и стоит понимать разницу:
- 400 — сервер не понял запрос: не хватает параметра или он неверного формата
- 401 — вы не авторизованы: токен не передан или недействителен
- 403 — авторизация есть, но прав на это действие нет
- 404 — такого ресурса не существует
- 429 — слишком много запросов, превышен лимит
Проверять код можно вручную, но у библиотеки есть готовый метод:
response = requests.get(url)
response.raise_for_status() # выбросит исключение при 4xx и 5xx
data = response.json()
raise_for_status() ничего не делает при успешном ответе и выбрасывает исключение HTTPError при ошибке. Это короткий способ не забыть проверку.
Заголовки запроса
Заголовки передают серверу служебную информацию: формат данных, язык, сведения о клиенте, токен доступа. Передаются они словарем:
headers = {
"User-Agent": "MyApp/1.0",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
Отдельно про User-Agent. По умолчанию библиотека представляется как python-requests, и часть сайтов такие запросы блокирует. Если получаете 403 там, где в браузере страница открывается, первым делом попробуйте задать собственный User-Agent.
Аутентификация: ключи и токены
Публичные API вроде GitHub работают без ключа, но с ограничением по числу запросов. Большинство сервисов требует авторизации, и вариантов обычно три.
Токен в заголовке — самый распространенный способ:
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(url, headers=headers)
Ключ в параметрах запроса — так устроены, например, погодные сервисы:
params = {"q": "Tashkent", "appid": api_key, "units": "metric"}
response = requests.get(base_url, params=params)
Базовая аутентификация логином и паролем:
response = requests.get(url, auth=("username", "password"))
Где хранить ключи и токены
Об этом почти не пишут в руководствах для новичков, а ошибка стоит дорого. Ключ, вписанный прямо в код, уезжает вместе с ним в репозиторий, и дальше им может воспользоваться кто угодно. Публичные ключи регулярно находят автоматические сканеры GitHub.
Правильный вариант — держать секреты в переменных окружения:
import os
import requests
api_key = os.getenv("WEATHER_API_KEY")
if not api_key:
raise RuntimeError("Не задан ключ WEATHER_API_KEY")
response = requests.get(url, params={"appid": api_key})
Локально удобно использовать файл .env и библиотеку python-dotenv, обязательно добавив .env в .gitignore. Правило простое: в коде не должно быть ни одного ключа, токена или пароля в открытом виде.
POST-запрос: отправляем данные
POST используют, когда нужно что-то создать или отправить. У библиотеки два разных аргумента, и их часто путают.
json= отправляет данные в формате JSON и сам проставляет нужный заголовок Content-Type. Это то, что нужно в 90 процентах случаев работы с API:
payload = {"title": "Новая заявка", "userId": 1}
response = requests.post(
"https://jsonplaceholder.typicode.com/posts",
json=payload
)
print(response.status_code) # 201
print(response.json())
data= отправляет данные как обычную веб-форму. Это нужно, когда API ожидает формат формы, а не JSON.
response = requests.post(url, data={"login": "user", "password": "123"})
Если перепутать и отправить data туда, где ждут JSON, сервер вернет 400 и невнятное сообщение. Это одна из самых частых причин, по которым запрос «не работает без причины».
Остальные методы вызываются так же: requests.put(), requests.patch(), requests.delete().
Таймаут: почему он обязателен
По умолчанию у запроса нет ограничения по времени. Если сервер не отвечает, программа будет ждать вечно — не упадет с ошибкой, а просто зависнет. В скрипте, запущенном по расписанию, это выглядит как «все сломалось, но ничего не написано в логах».
Поэтому timeout указывают всегда:
response = requests.get(url, timeout=10)
Число означает секунды. Можно задать раздельно время на подключение и на ожидание ответа:
response = requests.get(url, timeout=(3, 10))
Считайте это обязательным аргументом, даже если пишете учебный скрипт.
Обработка ошибок
Сеть — ненадежная среда, и ошибок здесь больше, чем в обычном коде. Все исключения библиотеки наследуются от RequestException, а внутри делятся по типам:
import requests
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
except requests.exceptions.Timeout:
print("Сервер не ответил вовремя")
except requests.exceptions.ConnectionError:
print("Нет соединения — проверьте интернет или адрес")
except requests.exceptions.HTTPError as e:
print("Сервер вернул ошибку:", e.response.status_code)
except requests.exceptions.RequestException as e:
print("Что-то пошло не так:", e)
else:
print(data)
Порядок блоков важен: конкретные исключения идут выше общего RequestException. Подробнее о том, как устроена обработка ошибок в Python, есть отдельная статья блога.
Повторные попытки и лимиты
Сетевые сбои часто разовые: сервер перегружен секунду и отвечает пятисотым кодом, а через две секунды работает нормально. Разумно повторить запрос, а не сдаваться сразу.
Простой вариант — цикл с паузой:
import time
for attempt in range(3):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
break
except requests.exceptions.RequestException:
if attempt == 2:
raise
time.sleep(2 ** attempt) # 1, 2, 4 секунды
Пауза здесь растет с каждой попыткой — это называется экспоненциальной задержкой и снижает нагрузку на и без того загруженный сервер.
Отдельно стоит код 429 : он означает, что вы превысили лимит запросов. Повторять сразу бессмысленно — сервис часто присылает в заголовке Retry-After число секунд, через которое можно попробовать снова. Уважайте лимиты: за их игнорирование ключ могут заблокировать.
Сессии: когда запросов много
Если к одному сервису идет несколько запросов подряд, создавайте сессию. Она переиспользует сетевое соединение, хранит cookies и общие заголовки — код становится и быстрее, и короче.
with requests.Session() as session:
session.headers.update({"Authorization": f"Bearer {token}"})
users = session.get("https://api.example.com/users", timeout=10)
posts = session.get("https://api.example.com/posts", timeout=10)
Заголовок с токеном задается один раз и применяется ко всем запросам внутри сессии. Контекстный менеджер with гарантирует, что соединение закроется.
Пагинация: забираем все страницы
API редко отдает тысячу записей одним ответом — обычно данные разбиты на страницы. Об этом почти не пишут в вводных статьях, хотя без пагинации не обходится ни один реальный проект.
Схемы бывают разные: номер страницы, смещение, курсор. Самая частая выглядит так:
all_items = []
page = 1
while True:
response = requests.get(
url,
params={"page": page, "per_page": 100},
timeout=10
)
response.raise_for_status()
items = response.json()
if not items:
break
all_items.extend(items)
page += 1
print("Всего получено:", len(all_items))
Цикл идет по страницам, пока сервер не вернет пустой список. Обратите внимание на extend вместо append — именно он склеивает списки в один плоский, о чем подробно рассказано в статье про эти методы.
Полезно добавить в цикл небольшую паузу и предохранитель на максимальное число страниц, чтобы случайная ошибка не превратилась в бесконечный цикл запросов.
Скачивание файлов
Для картинок и документов используют content, а для больших файлов — потоковый режим, чтобы не держать все в памяти:
response = requests.get(image_url, timeout=30)
with open("image.jpg", "wb") as file:
file.write(response.content)
# для больших файлов
with requests.get(file_url, stream=True, timeout=30) as response:
response.raise_for_status()
with open("archive.zip", "wb") as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
Файл открывается в режиме wb — запись в бинарном виде.
Практический пример: курс валют ЦБ Узбекистана
Центральный банк Узбекистана отдает курсы валют в JSON без всякой регистрации — удобный полигон для первого проекта.
import requests
def get_rate(currency: str = "USD"):
"""Возвращает курс валюты к суму по данным ЦБ РУз."""
url = f"https://cbu.uz/ru/arkhiv-kursov-valyut/json/{currency}/"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
except requests.exceptions.RequestException as e:
print("Не удалось получить курс:", e)
return None
if not data:
print("Валюта не найдена")
return None
item = data[0]
return {
"код": item["Ccy"],
"название": item["CcyNm_RU"],
"курс": float(item["Rate"]),
"дата": item["Date"],
}
rate = get_rate("USD")
if rate:
print(f"{rate['название']}: {rate['курс']} сум на {rate['дата']}")
В этом небольшом примере собрано почти все из статьи: параметр в адресе, таймаут, raise_for_status, разбор JSON, обработка ошибок и проверка пустого ответа. Без валюты в адресе тот же запрос вернет список всех курсов — его можно перебрать циклом.
Частые ошибки новичков
Забытый timeout. Программа зависает без сообщений об ошибке.
Вызов .json() без проверки. Если сервер вернул HTML или пустой ответ, метод упадет. Сначала raise_for_status(), потом разбор.
Ручная сборка URL со строками. Ведет к ошибкам с кодировкой. Используйте params.
Путаница между data и json. Отправляйте json= там, где API ждет JSON.
Токен прямо в коде. Утекает в репозиторий. Только переменные окружения.
Отключение проверки сертификата. Совет verify=False, который часто попадается на форумах, убирает ошибку, но делает соединение небезопасным. Правильный путь — обновить сертификаты, а не отключать проверку.
Игнорирование лимитов. Цикл без пауз быстро упирается в код 429 и блокировку ключа.
Проверка через if response.status_code == 200. Успешными бывают и коды 201, 204. Надежнее использовать response.ok или raise_for_status().
Чем заменить requests
Библиотека синхронная: пока идет запрос, программа ждет. Для сотни последовательных обращений это долго.
Если нужна асинхронность, смотрите в сторону httpx — у него почти такой же интерфейс, но есть асинхронный режим, — или aiohttp. Для парсинга сайтов requests часто сочетают с BeautifulSoup, а если страница собирается через JavaScript, обычного запроса не хватит и понадобится браузерная автоматизация.
Для типовых задач — сходить в API, забрать JSON, отправить данные — requests остается лучшим выбором. Не зря ее слоган переводится как «HTTP для людей».
Коротко о главном
requests превращает работу с HTTP в несколько понятных строк: get для получения данных, post для отправки, params для параметров, headers для заголовков и токенов, .json() для разбора ответа.
Отличие учебного скрипта от рабочего кода сводится к трем вещам: обязательный timeout, проверка ответа через raise_for_status() и обработка сетевых исключений. Добавьте к этому хранение ключей в переменных окружения и уважение к лимитам сервиса — и получится код, который можно запускать по расписанию и не бояться.
Умение забирать данные из внешних сервисов открывает целый пласт задач: телеграм-боты, парсеры, автоматические отчеты, интеграции. Освоить Python с нуля до уровня реальных проектов можно на курсе Python в PROWEB — 8 месяцев, с 14 лет, с практикой и поддержкой в трудоустройстве. Если вы еще выбираете направление в IT, пройдите бесплатный IT-тест — он подскажет, что вам ближе.
Часто задаваемые вопросы
Как установить библиотеку requests?
Командой pip install requests в терминале. Библиотека не входит в стандартную поставку Python, поэтому ее нужно ставить отдельно. Если команда не найдена, используйте python -m pip install requests.
Чем отличаются text, content и json()?
text возвращает тело ответа строкой, content — байтами, а json() разбирает JSON и возвращает словарь или список Python. Для работы с API нужен json(), для скачивания файлов и картинок — content.
Зачем нужен параметр timeout?
Без него запрос может ждать ответа бесконечно, и программа зависнет без всяких сообщений об ошибке. Указывайте timeout в каждом запросе — обычно достаточно 10 секунд.
Что делает метод raise_for_status?
Проверяет код ответа и выбрасывает исключение HTTPError, если сервер вернул ошибку из групп 4xx или 5xx. При успешном ответе метод не делает ничего. Это короткий способ не забыть проверку.
В чем разница между data и json в POST-запросе?
Аргумент json= отправляет данные в формате JSON и автоматически ставит нужный заголовок. Аргумент data= отправляет их как поля веб-формы. Большинство современных API ожидает именно JSON.
Что означает код ответа 401 и чем он отличается от 403?
401 означает, что вы не авторизованы: токен не передан или недействителен. 403 означает, что авторизация прошла, но прав на это действие у вас нет. В первом случае проверяйте токен, во втором — права доступа.
Как передать токен авторизации?
Чаще всего через заголовок: headers = {"Authorization": f"Bearer {token}"}. Некоторые API принимают ключ параметром в адресе. Сам токен храните в переменной окружения, а не в коде.
Почему сервер возвращает 403, хотя в браузере страница открывается?
Вероятно, сайт блокирует запросы по умолчанию — библиотека представляется как python-requests. Попробуйте задать свой заголовок User-Agent. Если не помогает, значит, требуется авторизация или сайт защищен от автоматических обращений.
Как забрать данные, если API отдает их по страницам?
Организовать цикл, который увеличивает номер страницы и запрашивает данные, пока сервер не вернет пустой результат. Полученные списки склеивайте методом extend, а между запросами делайте небольшую паузу.
Чем requests отличается от httpx и aiohttp?
requests работает синхронно: программа ждет ответа на каждый запрос. httpx предлагает почти такой же интерфейс, но умеет работать асинхронно, aiohttp изначально асинхронный. Для обычных задач с несколькими запросами requests проще и достаточно.



