Перейти к содержанию

172 уроков, 13 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 12 из 20 Средний 35 мин 130 XP

Собираем URL для API: urllib.parse

Схема, хост, путь, query: urlparse разбирает адрес, urlencode кодирует кириллицу в проценты, parse_qs читает строку запроса обратно — и весь запрос к API собирается без сети.

Редакция Питоники

До сих пор наши данные жили в переменных. Пора идти за настоящими — а настоящий API-запрос начинается не с сети, а с правильно собранного адреса. Фильтрация из прошлого урока учит сервер делать за нас: не качать все заказы и не отсеивать дома, а сразу попросить нужное параметрами в URL. Умение собрать такой адрес — половина работы с любым API.

Инструмент — стандартный модуль urllib.parse, и он полностью исполняется в браузерной песочнице: разбор и сборку адресов ты запускаешь кнопкой на странице, сеть для этого не нужна. Сеть понадобится позже — когда URL уже будет готов.

Анатомия URL: четыре части

Разберём адрес запроса на части. Схема — как говорить с сервером; хост — к какому серверу; путь — какой у него раздел; query — то, что после знака вопроса: параметры через амперсанд. Функция urlparse делает этот разбор за нас.

urlparse разбирает адрес
from urllib.parse import urlparse

url = "https://api.books.ru/v1/search?q=python&limit=5"
parts = urlparse(url)

print("Схема:", parts.scheme)
print("Хост:", parts.hostname)
print("Путь:", parts.path)
print("Query:", parts.query)
Вывод
Схема: https
Хост: api.books.ru
Путь: /v1/search
Query: q=python&limit=5
ЧастьПримерЗачем серверу
Схемаhttpsпротокол разговора
Хостapi.books.ruадрес сервера в интернете
Путь/v1/searchкакой раздел API звать
Queryq=python&limit=5параметры: что искать и сколько

Query — это пары «имя=значение», разделённые амперсандом. Именно его мы и будем собирать: сервер читает параметры из query и решает, что нам отдать. Собрать его руками нельзя — сейчас увидишь почему.

Параметрами API живут на полную: фильтры (status=paid), пагинация (page=2&limit=50), язык ответа (lang=ru), ключи доступа (api_key=...). Чем понятнее ты собираешь query, тем меньше работы остаётся серверу и тем меньше мусора приезжает обратно. Это та же идея, что в фильтрации на Python, только условие выполняет сервер: не качай всё — попроси нужное.

Атрибут urlparseПримерЧто это
schemehttpsпротокол
hostnameapi.books.ruхост без схемы
portNoneпорт, если указан явно
path/v1/searchпуть на сервере
queryq=python&limit=5строка параметров
fragmentNoneякорь после решётки

urlencode: словарь в строку запроса

В URL разрешены не все символы: только латиница, цифры и горстка знаков. Кириллица, пробелы, амперсанды внутри значений должны быть закодированы — каждая недопустимая буква превращается в процент и шестнадцатеричный код. Функция urlencode принимает словарь и возвращает готовую строку запроса:

urlencode - кириллица в проценты
from urllib.parse import urlencode

params = {"city": "Москва"}
query = urlencode(params)
print(query)
Вывод
city=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0

urlencode превращает словарь параметров в честную строку запроса: кириллица и пробелы кодируются так, как ждёт сервер. %D0%9C — это «М» в кодировке UTF-8, записанная шестнадцатеричными цифрами: шесть букв «Москва» разрослись в восемнадцать символов кода, но сервер раскодирует их обратно в точности.

Почему буквы превращаются в проценты? Стандарт URL разрешает в адресе только латиницу, цифры и несколько служебных знаков — текст едет по сети в надёжной ASCII-одежде, не путаясь с разделителями query вроде амперсанда и знака равенства. Каждая кириллическая буква в UTF-8 занимает два байта, и urlencode печатает их шестнадцатеричными парами: «М» — это D0 9C, отсюда %D0%9C.

несколько параметров и пробелы
from urllib.parse import urlencode

query = urlencode({"city": "Москва", "days": 3, "lang": "ru"})
print(query)

query2 = urlencode({"q": "пицца рядом", "limit": 5})
print(query2)
Вывод
city=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0&days=3&lang=ru
q=%D0%BF%D0%B8%D1%86%D1%86%D0%B0+%D1%80%D1%8F%D0%B4%D0%BE%D0%BC&limit=5

Обрати внимание на пробел: в query он живёт как знак +. Порядок параметров urlencode берёт из словаря — тот же, что ты записал. Числа он превращает в строки сам: days=3 получилось из целого числа без единого ручного str().

Значение-список urlencode тоже умеет: с параметром doseq=True каждый элемент списка превращается в отдельную пару с тем же именем. Так API принимают множественные фильтры — например, сразу два тега:

несколько значений одного параметра
from urllib.parse import urlencode

query = urlencode({"tag": ["книги", "техника"]}, doseq=True)
print(query)
Вывод
tag=%D0%BA%D0%BD%D0%B8%D0%B3%D0%B8&tag=%D1%82%D0%B5%D1%85%D0%BD%D0%B8%D0%BA%D0%B0

Без doseq=True urlencode превратил бы список в строку ['книги', 'техника'] — с квадратными скобками и кавычками внутри, что не поймёт ни один сервер. Порядок пар urlencode берёт из словаря — в Python он совпадает с порядком записи. Порядок параметров в query на результат почти никогда не влияет, но стабильный порядок делает URL предсказуемым в логах, тестах и кэшах.

parse_qs: строка обратно в словарь

Обратная дорога нужна реже, но без неё никуда: разобрать query, который пришёл тебе в callback-ссылке или в логах. parse_qs принимает строку запроса без вопросительного знака и возвращает словарь. С одним сюрпризом: значения — списки.

parse_qs разбирает обратно
from urllib.parse import parse_qs

query = "city=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0&days=3"
params = parse_qs(query)

print(params)
print(params["city"][0])
print(params["days"][0])
Вывод
{'city': ['Москва'], 'days': ['3']}
Москва
3

Откуда в жизни берутся строки запроса, которые хочется разобрать? Из callback-ссылок, куда сервис возвращает пользователя со своими параметрами; из ссылок из писем — там query несёт токен подтверждения; из логов, где requests печатают для отладки. parse_qs — универсальный ключ ко всем этим строкам: одна функция вместо ручного разрезания по знакам.

Собираем полный URL запроса

Теперь всё готово для главной сборки: базовый адрес, знак вопроса, urlencode — и у нас на руках настоящий запрос к API, какой бы отправила любая библиотека. Ничего не отправляем — просто печатаем:

полный URL без сети
from urllib.parse import urlencode

base = "https://api.weather.example/v1/forecast"
params = {"city": "Москва", "days": 3}

url = base + "?" + urlencode(params)
print(url)
Вывод
https://api.weather.example/v1/forecast?city=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0&days=3

Проверим себя полным кругом: соберём URL и тут же разберём его обратно, как это сделал бы сервер. urlparse отрежет query, parse_qs вернёт словарь — и мы должны увидеть те же значения, что закладывали.

полный круг: собрали - разобрали
from urllib.parse import urlencode, urlparse, parse_qs

base = "https://api.weather.example/v1/forecast"
url = base + "?" + urlencode({"city": "Сочи", "days": 5})
print(url)

parts = urlparse(url)
params = parse_qs(parts.query)
print(params["city"][0], "-", params["days"][0])
Вывод
https://api.weather.example/v1/forecast?city=%D0%A1%D0%BE%D1%87%D0%B8&days=5
Сочи - 5

Круг сошёлся: «Сочи» закодировалось в проценты и раскодировалось обратно. Этот же приём — самопроверка в тестах: собрал запрос, разобрал, сверил с ожиданием. Никакой сети для такой проверки не нужно.

В реальных запросах половина параметров необязательная: пользователь мог не указать цену, страницу, фильтр. Пустые строки в query лучше не отправлять вовсе — некоторые API трактуют max= как «ноль», и фильтр молча отсекает всё. Правило: собирай словарь только из заполненных значений:

только заполненные параметры
from urllib.parse import urlencode

raw = {"q": "чайник", "max": "", "page": 2}
params = {k: v for k, v in raw.items() if v != ""}
print(urlencode(params))
Вывод
q=%D1%87%D0%B0%D0%B9%D0%BD%D0%B8%D0%BA&page=2

Словарное включение с условием отфильтровало пустое значение ещё на входе — в query попали только настоящие параметры. Тот же приём в обратную сторону помогает при разборе: из parse_qs достают только те ключи, что реально пришли, не гадая заранее, что сервер положит в ответ.

И правило гигиены: в query не кладут пароли и долгоживущие секретные ключи сессий. Адрес с параметрами остаётся в истории браузера, логах серверов и прокси — надёжнее передавать их в теле запроса или заголовках. Ключи доступа к публичным API вида api_key=... — исключение для учебных сервисов, но и к ним стоит относиться как к не совсем публичным строкам: не выкладывать в репозиторий и перевыпускать при утечке.

quote: кодируем кусок пути

Параметры — не единственное место для кириллицы: город может сидеть и в пути, как /city/Казань. Для одиночного куска URL есть quote — он кодирует строку целиком, без пар ключ-значение, и оставляет пробел в виде %20, как положено в пути.

quote для куска пути
from urllib.parse import quote

city = "Нижний Новгород"
print(quote(city))
print("https://geo.example/city/" + quote(city))
Вывод
%D0%9D%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%D0%9D%D0%BE%D0%B2%D0%B3%D0%BE%D1%80%D0%BE%D0%B4
https://geo.example/city/%D0%9D%D0%B8%D0%B6%D0%BD%D0%B8%D0%B9%20%D0%9D%D0%BE%D0%B2%D0%B3%D0%BE%D1%80%D0%BE%D0%B4
руками против urlencode
from urllib.parse import urlencode

city = "Ростов-на-Дону"

bad = "https://api.example/forecast?city=" + city
good = "https://api.example/forecast?" + urlencode({"city": city})
print("Руками:", bad)
print("urlencode:", good)
Вывод
Руками: https://api.example/forecast?city=Ростов-на-Дону
urlencode: https://api.example/forecast?city=%D0%A0%D0%BE%D1%81%D1%82%D0%BE%D0%B2-%D0%BD%D0%B0-%D0%94%D0%BE%D0%BD%D1%83

Вариант «руками» выглядит прилично лишь потому, что в городе не оказалось пробела или амперсанда. Добавь «Ростов-на-Дону, ТЦ» — и ручная склейка сломает запрос, а urlencode спокойно закодирует всё, включая запятую.

Дорога к настоящему запросу

URL готов — как им воспользоваться? Отправить запрос может только код с доступом к сети, а у браузерной песочницы такого доступа нет — это честное ограничение, а не упрощение. Вот как выглядит отправка на своём компьютере стандартной библиотекой:

сам запрос - только со своего компьютера
# Только на своём компьютере: песочница без сети
from urllib.request import urlopen
from urllib.parse import urlencode

url = "https://api.weather.example/v1/forecast?" + urlencode({"city": "Москва"})
with urlopen(url) as r:
    body = r.read().decode("utf-8")     # тело ответа - текст
print(body)
Блок для запуска на своём компьютере: urlopen ходит в сеть, которой у песочницы нет. Сама сборка URL через urlencode исполняется на странице без проблем — не запускается именно отправка.

В следующих уроках мы распечатаем этот путь дальше: что именно сервер возвращает в ответе и как разобрать его текст в данные. Ответ приходит текстом — и вся механика разбора, как и сегодня, исполняется в песочнице честно.

Функция сборки запроса

Оформим сборку как функцию — так её удобно звать для разных разделов API, а сам приём **params принимает любые параметры без длинных словарей в аргументах.

функция build_url
from urllib.parse import urlencode, urlparse, parse_qs

def build_url(base, **params):
    return base + "?" + urlencode(params)

url = build_url("https://api.shop.example/v1/products", category="книги", limit=5)
print(url)

params = parse_qs(urlparse(url).query)
print(params["category"][0], params["limit"][0])
Вывод
https://api.shop.example/v1/products?category=%D0%BA%D0%BD%D0%B8%D0%B3%D0%B8&limit=5
книги 5

Одна функция — и любой запрос собирается в одну строку: адрес базы плюс сколько угодно именованных параметров. Это готовый кирпич для будущих скриптов: обернёшь в запрос — получишь клиент, обернёшь в тест — получишь самопроверку.

Есть у query и физический потолок: адрес длиннее пары тысяч символов некоторые серверы и прокси отрезают молча. Десяток параметров — нормально; словарь на сотню значений — повод задуматься о другом методе запроса, где данные едут в теле, а не в адресе. Для учебных и большинства рабочих API лимита хватает с запасом.

Готовая функция сборки окупается сразу: URL можно печатать в лог перед отправкой — и отлаживать запросы глазами, не открывая сетевой трассировщик. Половина «мистических» ошибок API оказывается опечаткой в параметре, которую печать URL показывает мгновенно.

Хороший запрос к API рождается до сети: разобранная анатомия адреса, urlencode для данных и parse_qs для самопроверки.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

from urllib.parse import urlencode
print(urlencode({"page": 2, "tag": "книги"}))
from urllib.parse import parse_qs
p = parse_qs("a=1&b=x&b=y")
print(len(p), p["b"][1])
from urllib.parse import urlparse
u = urlparse("https://api.site.ru/v2/items?page=1")
print(u.hostname, u.path)
Проверь себя
0 / 5

1. Что делает urlencode({"page": 2})?

2. Что вернёт parse_qs("a=1&b=x")?

3. Куда urlencode денет пробел в значении «пицца рядом»?

4. params = parse_qs(query). Как достать город?

5. Что содержит urlparse(url).query?

Карточки терминов
Запомнено: 0 / 6
Практика

Собери URL поиска по базе товаров: база https://api.shop.example/v1/search и два параметра — q со значением «чайник» и max со значением 3000. Выведи URL, затем разбери его через urlparse и parse_qs и выведи параметр q и параметр max одной строкой.

practice.py
Вопросы и ответы по уроку

Как добавить параметры к URL запроса в Python?

Через urlencode из стандартного модуля urllib.parse: url = base + "?" + urlencode({"q": "запрос", "limit": 10}). Функция сама закодирует кириллицу, пробелы и служебные символы. Обратный разбор — urlparse(url).query и parse_qs.

Почему кириллица в URL превращается в проценты?

Стандарт URL разрешает только латиницу, цифры и несколько знаков; всё остальное кодируется процентом и шестнадцатеричным кодом байта UTF-8. %D0%9C — это «М». Сервер обязан раскодировать такие последовательности обратно, поэтому передача кириллицы через urlencode безопасна.

Чем urlencode отличается от quote?

urlencode кодирует словарь параметров в целую строку «ключ=значение&ключ=значение», а quote — одну строку целиком, для куска пути. В query пробел становится плюсом, в quote — %20: поэтому для параметров берут urlencode, для путей — quote.

Как отправить собранный URL на сервер?

Кодом с доступом к сети: urlopen из urllib.request или requests.get на своём компьютере. Браузерная песочница самоучителя сети не имеет, но сборка и разбор URL работают прямо на странице — а механику ответа разбираем в следующих уроках на сохранённом тексте.

Понравился урок? Сошлитесь на него

«urlencode превращает словарь параметров в честную строку запроса: кириллица и пробелы кодируются так, как ждёт сервер.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.