← Назад к вопросам
Какая структура данных подойдет для хранения 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, добавляй оптимизацию только когда выявишь узкие места. Преждевременная оптимизация — враг простоты кода.