Работа с URL: ссылки и параметры пагинации
Скрейпер постоянно собирает URL и разбирает их параметры — это отдельный навык.
Модульurllib.parseумеет разбирать query-string в словарь, собирать URL обратно и превращать относительные ссылки (/page2) в абсолютные. Без этого нельзя обходить каталоги.
Найдя на странице ссылки, ты часто получаешь их в относительном виде: href="/catalog/123". Чтобы перейти по ним, нужен полный адрес. А чтобы листать страницы, нужно уметь менять параметр page в query-string. Всё это решает стандартный urllib.parse — и он работает в браузере.
Относительные ссылки → абсолютные
Функция urljoin соединяет базовый URL страницы с относительной ссылкой, как это делает браузер:
Попробуй сам ▶
from urllib.parse import urljoin
base = 'https://shop.example/catalog/page2'
links = ['/product/1', '../about', 'page3', 'https://ext.io/x']
for link in links:
print(f'{link:20} -> {urljoin(base, link)}')Сборка query-string для пагинации
Чтобы листать каталог, скрейпер меняет параметры и собирает URL заново через urlencode:
Попробуй сам ▶
from urllib.parse import urlencode, urlparse, parse_qs, urlunparse
url = 'https://shop.example/catalog?sort=price&page=1'
p = urlparse(url)
params = parse_qs(p.query)
# построим адреса страниц 1..3
for page in range(1, 4):
params['page'] = [str(page)]
query = urlencode(params, doseq=True)
new = urlunparse(p._replace(query=query))
print(new)Как работает под капотом
urlparse раскладывает URL на шесть частей (схема, хост, путь, параметры, query, фрагмент). parse_qs превращает page=1&sort=price в словарь со списками значений. После изменения urlencode и urlunparse собирают всё обратно в корректный адрес — с правильным экранированием спецсимволов и кириллицы. Это надёжнее ручной склейки строк через +.
Нормализация URL и борьба с дублями
При обходе сайта один и тот же ресурс встречается под разными адресами: с якорем #section и без, с разным порядком query-параметров, со слешем на конце и без. Если не нормализовать URL, скрейпер будет ходить по одной странице многократно — это и лишняя нагрузка на сайт, и дубли в данных. Нормализация приводит адреса к каноничному виду: убрать фрагмент #, отсортировать параметры, привести хост к нижнему регистру, решить вопрос с завершающим слешем.
Связанная задача — оставаться в пределах одного домена. Перейдя по ссылке, легко случайно «уйти» на внешний сайт и начать скрейпить чужой ресурс без спроса. Поэтому после urljoin сравнивают netloc полученного URL с целевым доменом и отбрасывают чужие ссылки. Множество уже посещённых адресов (set) плюс проверка домена — это, по сути, ручная реализация того, что Scrapy делает автоматически своим планировщиком с дедупликацией. Понимая механику, ты осознанно контролируешь, куда именно ходит твой бот.
Частые ошибки
- Склеивать URL вручную.
base + '/' + linkломается на../, лишних слешах и абсолютных ссылках.urljoinучитывает все случаи. - Не экранировать параметры. Пробелы и кириллица в query ломают запрос;
urlencodeкодирует их правильно. - Забывать про
doseq=True. Без него списки значений кодируются криво.
Best practices
- Любую относительную ссылку прогоняй через
urljoin(base, link). - Для построения URL с параметрами используй
urlencode, а не конкатенацию строк. - Нормализуй URL (убирай дубли, якоря
#) перед добавлением в очередь обхода.
Когда обход становится большим, очередь URL и множество посещённых адресов лучше держать не только в памяти, но и на диске или в базе — чтобы прерванный краулинг можно было продолжить, а не начинать заново и снова грузить сайт. Это та же идея, что заложена в планировщик Scrapy с его персистентной очередью. Даже если ты пишешь обход вручную на requests, полезно с самого начала думать о нём как о системе «очередь → посещено → результаты», а не как о простом цикле: так код легче масштабировать и делать вежливым.
Ещё одна частая тонкость — относительные ссылки бывают разных видов: начинающиеся со слеша (/catalog, от корня сайта), с двумя точками (../page, на уровень выше), без префикса (page3, относительно текущей папки) и протокол-относительные (//cdn.example/img). Самостоятельно учитывать все эти случаи утомительно и легко ошибиться, поэтому единая функция urljoin и нужна: она реализует ровно ту же логику разрешения адресов, что и браузер, по стандарту. Доверяя ей, ты получаешь корректные абсолютные URL во всех ситуациях и не плодишь тонких багов в обходе сайта.
Итог: работа с URL — рутина скрейпинга. urljoin превращает относительные ссылки в абсолютные, а urlencode/urlunparse аккуратно собирают адреса для пагинации.