← Назад к вопросам

Какая структура данных подойдет для хранения URL в Django?

1.8 Middle🔥 161 комментариев
#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Структура данных для хранения URL в Django

Этот вопрос практический — зависит от того, что ты хочешь делать с URL. Разберу разные сценарии и подходы.

1. Простой случай — просто хранишь URL

Если просто нужно сохранить полный URL в БД:

from django.db import models

class Link(models.Model):
    # URLField валидирует URL формат
    url = models.URLField(max_length=2048)
    
    # или SimpleURLField если нужна большая длина
    long_url = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

Характеристики URLField:

  • Максимальная длина: 200 символов (по умолчанию)
  • Валидация: проверяет, что это корректный URL
  • Можно увеличить max_length

Когда использовать:

  • Простые приложения (блоги, справочники)
  • Не нужна быстрая работа с компонентами URL

2. Разделенное хранилище — парсишь URL на компоненты

Если нужна работать с компонентами URL (схема, хост, путь и т.д.):

from django.db import models
from urllib.parse import urlparse

class ParsedURL(models.Model):
    # Компоненты URL
    scheme = models.CharField(max_length=10, default='https')
    domain = models.CharField(max_length=255, db_index=True)
    path = models.TextField(default='')
    query_params = models.JSONField(default=dict)
    fragment = models.CharField(max_length=255, blank=True)
    
    # Полный URL для быстрого доступа
    full_url = models.URLField(unique=True)
    
    created_at = models.DateTimeField(auto_now_add=True)
    
    def save(self, *args, **kwargs):
        # Парсим URL при сохранении
        if not self.scheme:  # Только если не заполнено
            parsed = urlparse(self.full_url)
            self.scheme = parsed.scheme
            self.domain = parsed.netloc
            self.path = parsed.path
            self.fragment = parsed.fragment
            # query_params = dict(parse_qsl(parsed.query))
        super().save(*args, **kwargs)
    
    class Meta:
        indexes = [
            models.Index(fields=['domain', 'path']),
            models.Index(fields=['created_at']),
        ]

Преимущества:

  • Быстрая фильтрация по домену
  • Просто искать по пути
  • Контролируешь формат данных

Недостатки:

  • Больше места в БД
  • Нужна синхронизация (парсинг при сохранении)

3. Нормализация доменов — деноrmализованное хранилище

Если много URL от одного домена — вынеси домены в отдельную таблицу:

from django.db import models
from django.db.models import Q

class Domain(models.Model):
    name = models.CharField(max_length=255, unique=True, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True)

class URLEntry(models.Model):
    domain = models.ForeignKey(Domain, on_delete=models.CASCADE, 
                               related_name='urls')
    path = models.TextField()
    query = models.TextField(blank=True)
    fragment = models.CharField(max_length=255, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    @property
    def full_url(self):
        url = f"https://{self.domain.name}{self.path}"
        if self.query:
            url += f"?{self.query}"
        if self.fragment:
            url += f"#{self.fragment}"
        return url
    
    class Meta:
        unique_together = ('domain', 'path', 'query')
        indexes = [
            models.Index(fields=['domain', 'created_at']),
            models.Index(fields=['path']),
        ]

# Использование
site_com, _ = Domain.objects.get_or_create(name='example.com')
url = URLEntry.objects.create(
    domain=site_com,
    path='/blog/post-123',
    query='sort=date&limit=10'
)

Преимущества:

  • Экономия памяти (домены не повторяются)
  • Быстрые запросы по домену
  • Просто найти все URL от одного домена

Недостатки:

  • JOIN'ы при каждом запросе
  • Сложность при удалении (каскадное удаление)

4. Оптимизация для поиска — использование Trie или индексов

Если нужна быстрая работа с URL-префиксами (например, поиск всех URL, начинающихся с /api/v1/):

from django.db import models

class URLPath(models.Model):
    # Используем триграммный индекс для LIKE запросов
    path = models.TextField(db_index=True)
    hits = models.IntegerField(default=0)
    
    class Meta:
        # Для PostgreSQL: триграммный индекс
        indexes = [
            models.Index(fields=['path'], name='url_path_idx'),
        ]
        # GIN индекс для полнотекстового поиска
        # В PostgreSQL это даст O(log N) вместо O(N)

# Запрос
from django.db.models import Q
matches = URLPath.objects.filter(path__startswith='/api/v1/')

5. Кеш часто используемых URL

Для высоконагруженных систем:

from django.db import models
from django.core.cache import cache
import hashlib

class CachedURL(models.Model):
    # Хеш URL для быстрого поиска
    url_hash = models.CharField(max_length=64, unique=True, db_index=True)
    full_url = models.URLField(unique=True)
    hits = models.IntegerField(default=0)
    cached_at = models.DateTimeField(auto_now=True)
    
    @staticmethod
    def get_or_create_with_hash(url):
        url_hash = hashlib.sha256(url.encode()).hexdigest()
        obj, created = CachedURL.objects.get_or_create(
            url_hash=url_hash,
            defaults={'full_url': url}
        )
        return obj, created
    
    def get_cached_result(self, cache_key='url_'):
        key = cache_key + str(self.id)
        result = cache.get(key)
        if not result:
            result = self._compute_result()
            cache.set(key, result, 3600)  # 1 час
        return result
    
    def _compute_result(self):
        # Твоя логика обработки URL
        return {"url": self.full_url, "valid": True}

6. JSONB для сложных структур (PostgreSQL)

Если нужна гибкость и Postgres:

from django.db import models

class FlexibleURL(models.Model):
    # Хранишь весь парсинг URL в JSON
    data = models.JSONField()
    
    @classmethod
    def from_url(cls, url):
        from urllib.parse import urlparse, parse_qs
        parsed = urlparse(url)
        return cls(data={
            'full_url': url,
            'scheme': parsed.scheme,
            'netloc': parsed.netloc,
            'path': parsed.path,
            'params': dict(parse_qs(parsed.query)),
            'fragment': parsed.fragment,
        })
    
    class Meta:
        indexes = [
            models.Index(fields=['data']),  # Для JSON поиска
        ]

# Запрос с фильтром по JSON
urls = FlexibleURL.objects.filter(
    data__scheme='https',
    data__netloc__contains='example.com'
)

Моя рекомендация по выбору

Сценарий                              | Рекомендуемая структура
--------------------------------------|------------------------
Просто сохранить URL                  | URLField
Работать с компонентами URL           | Разделенные поля
Много URL от одного домена            | ForeignKey на Domain
Поиск по префиксам пути               | Индексы + startswith
Сложная структура данных, PostgreSQL  | JSONField
Высоконагруженная система            | Кеширование + хеши
Быстрые фильтры по доменам/путям    | Составные индексы

Практический пример для типичного приложения

class Link(models.Model):
    # Для большинства случаев достаточно
    url = models.URLField(max_length=2048, unique=True)
    title = models.CharField(max_length=255, blank=True)
    description = models.TextField(blank=True)
    
    # Для удобства поиска
    domain = models.CharField(max_length=255, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    
    def save(self, *args, **kwargs):
        from urllib.parse import urlparse
        if not self.domain:
            self.domain = urlparse(self.url).netloc
        super().save(*args, **kwargs)
    
    class Meta:
        indexes = [
            models.Index(fields=['domain', 'created_at']),
        ]
        verbose_name_plural = 'links'

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

Какая структура данных подойдет для хранения URL в Django? | PrepBro