ГлавнаяБлогКластеризация URL: как отличить структуру от идентификатора
Алгоритмы

Кластеризация URL: как отличить структуру от идентификатора

Кластеризация URL помогает краулерам отличать структурные страницы от идентификаторов. Узнайте, как построить устойчивый алгоритм без ML.

Al
Редакция Algolitalgolit.ru
8 мин чтения12 августа 2026 г.

Кластеризация URL: почему это важно для краулера

Вы когда-нибудь задумывались, как поисковый робот понимает, что /catalog/iphone-15 — это страница товара, а /product/12345 — просто идентификатор? На самом деле, это сложная задача, особенно на крупных сайтах. В этой статье я расскажу, как мы решили проблему кластеризации URL для эффективного обхода сайтов, используя простые эвристики и адаптивное обучение. Вы узнаете, как отличить структурные URL от идентификаторов и почему чистые правила всегда проигрывают.

Почему эвристики не работают: боль больших сайтов

Когда я впервые столкнулся с задачей, мне казалось, что можно просто посмотреть на URL и понять его тип. Но реальность оказалась сложнее. Паттерн-матчинг разбивался об URL с нестандартными слагами, а Trie-структуры не помогали на больших e-commerce сайтах, где URL генерируются хаотично. Детерминированные правила (например, «если есть число — это ID») работали только для хэшей и чисел, но не для контентных слагов, которые могут быть короткими, без дефисов и содержать буквы с цифрами.

Проблема с обобщёнными моделями

Первая попытка — обучить модель на основе наивного Байеса. Прототип на тестовых URL показал неплохие результаты, но на реальных данных модель требовала огромного датасета и пайплайна. Я понял, что моих навыков в ML недостаточно, и решил попробовать другие подходы.

Итерация 1: Наивный Байес и его ограничения

Я обучил модель на размеченных URL, и она научилась определять «позитивные» пути. Но на продакшене данных было слишком много, и обобщение требовало больше данных и лучшего пайплайна. Осознав, что я не настолько силён в ML, я отказался от этого подхода.

Итерация 2: Локальный словарь поверх глобальной модели

Следующая идея — добавить локальный словарь для каждого сайта. Глобальная модель работала для общих слов, но не понимала, что application/marine может быть крупным хабом. Локальный словарь должен был заполнять пробелы. Но проблема: то, что редко встречалось в общей выборке, было частым на конкретном сайте, и наоборот. Подбор весов для глобального и локального скоринга превратился в охоту на призраков.

Итерация 3: Полный отказ от глобальной модели

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

Итерация 4: Адаптивная кластеризация URL с обратной связью

Я перебрал несколько идей: фейковый обход для обучения, ограничение в 100 страниц для сбора словаря — всё это было либо дорого, либо ненадёжно. В итоге я понял: нужно строить устойчивость. Как человек, мы начинаем с догадки, накапливаем данные и уточняем кластеры. Так и здесь: мы добавили чекпоинты каждые 50 загрузок, пересчитывали паттерны URL с обновлённым словарём и ввели защитные механизмы для оценки «редкости» и «структурности». Редкие ключевые слова на сайте могут быть не только 1,2,3, а структурные — не только 40,50. В начале мы не знаем этих порогов, но по мере обхода они уточняются, и кластеры становятся всё более различимыми.

Реализация на Python: базовый алгоритм

Ниже — упрощённая реализация идеи на Python. Мы собираем статистику по сегментам URL, вычисляем их частоту и на основе этого относим сегмент к структурному или идентификаторному.

from collections import Counter
import re

# Функция разбивает URL на сегменты (пути)
def get_segments(url):
    path = url.split('?')[0]  # убираем query-параметры
    return path.split('/')[1:]  # пропускаем первый пустой элемент

# Класс для адаптивной кластеризации
class AdaptiveURLCluster:
    def __init__(self, threshold=0.1):
        self.segment_counts = Counter()  # счётчики сегментов
        self.threshold = threshold  # порог для определения структурности
        self.total_urls = 0

    def update(self, urls):
        '''Обновляем статистику на основе новых URL'''
        for url in urls:
            segments = get_segments(url)
            for seg in segments:
                self.segment_counts[seg] += 1
            self.total_urls += 1
        # Пересчитываем порог на основе текущих данных
        self._recalibrate_threshold()

    def _recalibrate_threshold(self):
        '''Пересчитываем порог: редкие сегменты — идентификаторы'''
        if self.total_urls == 0:
            return
        # Считаем среднюю частоту сегмента
        avg_freq = sum(self.segment_counts.values()) / len(self.segment_counts)
        # Порог — доля от средней частоты
        self.threshold = avg_freq * 0.1

    def classify_segment(self, segment):
        '''Определяем тип сегмента: структурный или идентификатор'''
        freq = self.segment_counts.get(segment, 0)
        if freq == 0:
            return 'unknown'
        if freq / self.total_urls >= self.threshold:
            return 'structural'
        else:
            return 'identifier'

# Пример использования
cluster = AdaptiveURLCluster()
# Первая партия URL (имитация обхода)
urls_batch1 = [
    '/catalog/iphone-15',
    '/catalog/samsung-galaxy',
    '/product/12345',
    '/product/67890',
    '/blog/10-tips-for-coding'
]
cluster.update(urls_batch1)

# Проверяем классификацию
for url in urls_batch1:
    segments = get_segments(url)
    types = [cluster.classify_segment(seg) for seg in segments]
    print(url, types)

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

Практический вывод: как применить это в своём проекте

Главный урок — не пытайтесь угадать структуру сайта заранее. Вместо этого создайте систему, которая самообучается на лету. Начните с простого счётчика сегментов, добавьте периодический пересчёт порогов и защитные механизмы. Это поможет вам эффективно обходить даже самые хаотичные сайты. Попробуйте реализовать такой адаптивный кластеризатор в своём краулере — и вы увидите, как сократится время обхода и количество бесполезных запросов.

#кластеризация URL#краулер#адаптивные алгоритмы#Python
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →