Библиотека requests в Python: запросы к API с нуля

Библиотека 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 проще и достаточно.



Другие статьи