Реалізація дедуплікації скрейплених даних
Скрейпінг кількох джерел неминуче призводить до дублів: один товар присутній на сайті виробника, у трьох дистрибюторських каталогах та на маркетплейсі. Наївне порівняння по URL або назві працює погано — потрібні більш розумні підходи.
Рівні дедуплікації
Рівень 1 — Точне збігання. По нормалізованому ключу: SKU, EAN/GTIN, артикул виробника. Найнадійніший спосіб, працює там, де є унікальний ідентифікатор.
def normalize_sku(raw_sku: str) -> str:
# прибираємо пробіли, дефіси, приводимо до верхнього регістру
return re.sub(r'[\s\-_/]', '', raw_sku).upper()
Рівень 2 — Хешування контенту. Для контенту (статті, описи) — нормалізуємо текст та рахуємо хеш.
def content_hash(text: str) -> str:
normalized = ' '.join(text.lower().split()) # прибираємо зайві пробіли
return hashlib.sha256(normalized.encode()).hexdigest()
Рівень 3 — Нечітке збігання (fuzzy matching). Для товарів без явного SKU — порівняння назв за відстанню Левенштейна або алгоритмами Token Sort/Token Set Ratio.
from rapidfuzz import fuzz, process
def find_duplicate(new_title: str, existing_titles: list[str], threshold=85):
result = process.extractOne(
new_title,
existing_titles,
scorer=fuzz.token_sort_ratio
)
if result and result[1] >= threshold:
return result[0]
return None
token_sort_ratio сортує слова перед порівнянням — добре працює з перестановками слів у назвах товарів.
Рівень 4 — Векторна схожість. Для текстів з семантичним значенням — embeddings через sentence-transformers та cosine similarity.
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
def are_similar(text1: str, text2: str, threshold=0.92) -> bool:
embeddings = model.encode([text1, text2])
cosine_sim = np.dot(embeddings[0], embeddings[1]) / (
np.linalg.norm(embeddings[0]) * np.linalg.norm(embeddings[1])
)
return float(cosine_sim) >= threshold
Для великих обсягів — індекс у pgvector (PostgreSQL) або Milvus для наближеного пошуку за векторами.
Продуктивність
При мільйонах записів попарне порівняння неприйнятне. Стратегії:
- MinHash + LSH (Locality Sensitive Hashing) — швидкий пошук кандидатів на дубли в великих наборах текстів
- Blocking — спочатку фільтруємо за точними атрибутами (категорія, цінова категорія), потім нечітке порівняння тільки всередину блоку
-
Індекси в PostgreSQL —
pg_trgmдля нечіткого пошуку по строках зsimilarity()та%оператором
-- Встановлення розширення
CREATE EXTENSION pg_trgm;
CREATE INDEX ON products USING GIN (title gin_trgm_ops);
-- Пошук подібних назв
SELECT id, title, similarity(title, 'Iphone 15 pro max 256') AS sim
FROM products
WHERE title % 'Iphone 15 pro max 256'
ORDER BY sim DESC
LIMIT 10;
Керування дублями
Знайдені дубли не видаляються автоматично. Система формує групи кандидатів з обчисленим score збігання. Фінальне рішення — або автоматичне (при score > 95%), або через інтерфейс ручної перевірки.
Час реалізації системи дедуплікації з кількома рівнями: 4–7 робочих днів.







