Урок 7 из 8 · 20 мин · обновлено
Данные из интернета: запросы к API и JSON
Как получать данные из интернета на Python: что такое API и JSON, запросы через библиотеку requests, курсы валют ЦБ, пересчёт цен поставщика в рубли, обработка ошибок сети и сохранение данных в файл.
API и JSON
«Канцмаркет» закупает часть товаров у поставщика в Китае, цены в прайсе — в юанях. Каждую неделю кто-то открывает сайт ЦБ, смотрит курс и пересчитывает цены в калькуляторе. Поручим это скрипту.
Сайты сделаны для людей: с картинками, меню, рекламой. Программе удобнее другое — API: адрес, по которому сервис отдаёт чистые данные. Чаще всего в формате JSON. Он очень похож на словари и списки Python:
{
"Date": "2026-10-28T11:30:00+03:00",
"Valute": {
"USD": {
"CharCode": "USD",
"Nominal": 1,
"Name": "Доллар США",
"Value": 80.5307,
"Previous": 80.4909
}
}
}
Это кусочек ответа с курсами валют. Курсы устанавливает Центральный банк, сам ЦБ публикует их на cbr.ru в формате XML. Мы возьмём те же данные в JSON с сайта cbr-xml-daily.ru — так работать проще.
Запрос через requests
Для запросов в интернет нужна библиотека requests, мы поставили её в уроке 1.
import requests
URL = "https://www.cbr-xml-daily.ru/daily_json.js"
response = requests.get(URL, timeout=10)
response.raise_for_status() # если сервер ответил ошибкой — остановиться
data = response.json()
print("Курсы на", data["Date"][:10])
for code in ["USD", "EUR", "CNY"]:
valute = data["Valute"][code]
rate = valute["Value"] / valute["Nominal"]
print(f"{code} {valute['Name']}: {rate:.2f} ₽")
Курсы на 2026-10-28
USD Доллар США: 80.53 ₽
EUR Евро: 90.10 ₽
CNY Юань: 12.07 ₽
У вас курсы будут на сегодняшний день. Что делает каждая строка:
requests.get(URL, timeout=10)отправляет запрос и ждёт ответа не дольше 10 секунд;raise_for_status()останавливает скрипт, если сервер ответил ошибкой — например, «страница не найдена»;response.json()превращает JSON в словари и списки Python;data["Valute"]["CNY"]— идём по вложенным ключам, как в обычном словаре.
Nominal — за сколько единиц валюты указан курс. У доллара и юаня это 1, а у некоторых валют — 10 или 100. Поэтому курс за одну единицу — всегда Value / Nominal.
Пересчитываем прайс
Теперь пересчитаем цены поставщика:
import requests
prices_cny = {"Пенал школьный": 18.5, "Набор маркеров": 32.0, "Рюкзак": 145.0}
data = requests.get("https://www.cbr-xml-daily.ru/daily_json.js", timeout=10).json()
cny = data["Valute"]["CNY"]
rate = cny["Value"] / cny["Nominal"]
for product, price in prices_cny.items():
print(f"{product}: {price} ¥ → {price * rate:,.0f} ₽".replace(",", " "))
Пенал школьный: 18.5 ¥ → 223 ₽
Набор маркеров: 32.0 ¥ → 386 ₽
Рюкзак: 145.0 ¥ → 1 750 ₽
В формате {price * rate:,.0f} запятая разделяет разряды, а .0f округляет до целых. Python ставит между разрядами запятую — .replace(",", " ") меняет её на привычный пробел. В настоящем скрипте прайс, конечно, читается из таблицы, как в уроке 3.
Когда что-то пошло не так
Интернет ненадёжен: сервер может не ответить, сайт — переехать, Wi-Fi — пропасть. Скрипт, который работает по расписанию, должен понятно сообщать о таких проблемах, а не падать с длинной ошибкой. Все ошибки requests ловятся одним исключением:
try:
response = requests.get(URL, timeout=10)
response.raise_for_status()
data = response.json()
except requests.RequestException as error:
print("Не удалось получить курсы:", error)
raise SystemExit(1)
Если, например, адрес неправильный, скрипт напишет:
Не удалось получить курсы: 404 Client Error: Not Found for url: https://www.cbr-xml-daily.ru/no-such
raise SystemExit(1) завершает скрипт с кодом ошибки — по нему планировщик из урока 8 поймёт, что запуск не удался.
Ещё два правила вежливости к чужим API: не отправляйте запросы чаще, чем нужно (курсы ЦБ меняются раз в день), и читайте условия сервиса. Многие API требуют ключ — его, как и пароль от почты, хранят в переменной окружения.
Попробуйте сами. Сохраните курсы всех валют за сегодня в файл курсы.json: дату и словарь «код валюты — курс за одну единицу». Добавьте обработку ошибок сети.
Решение
import json
import requests
URL = "https://www.cbr-xml-daily.ru/daily_json.js"
try:
response = requests.get(URL, timeout=10)
response.raise_for_status()
data = response.json()
except requests.RequestException as error:
print("Не удалось получить курсы:", error)
raise SystemExit(1)
rates = {code: v["Value"] / v["Nominal"] for code, v in data["Valute"].items()}
with open("курсы.json", "w", encoding="utf-8") as f:
json.dump({"date": data["Date"][:10], "rates": rates}, f, ensure_ascii=False, indent=2)
print("Сохранено валют:", len(rates))
Сохранено валют: 54
{code: ... for code, v in ...} — генератор словаря: короткая запись цикла, который собирает новый словарь. indent=2 делает файл читаемым, а ensure_ascii=False сохраняет русские буквы как есть. Прочитать файл обратно можно через json.load(f).
Мини-тест · 4 вопроса
Проверьте себя
Выберите вариант, который кажется вам правильным. В конце вы увидите свой результат и объяснение к каждому вопросу.
-
Что такое API сервиса?
Сайт сделан для людей, а API — для программ. По адресу API сервис отдаёт данные, обычно в формате JSON.
-
data = response.json(). Как достать курс юаня из ответа вида {"Valute": {"CNY": {"Value": 12.07}}}?
JSON превращается во вложенные словари. Идём по ключам сверху вниз: Valute → CNY → Value.
-
Зачем указывать timeout=10 в requests.get()?
Без timeout запрос может ждать ответа бесконечно. С ним через 10 секунд будет ошибка, которую можно обработать.
-
Почему у курса нужно учитывать поле Nominal?
ЦБ публикует курсы мелких валют за 10 или 100 единиц. Если не разделить на Nominal, цена окажется в десятки раз больше.