Prowadzimy katalog wypożyczalni rentabe.sk. Ze względu na dynamiczny rozwój asortymentu, wdrożyliśmy automatyzację kategoryzacji, aby wyeliminować konieczność ręcznego przypisywania produktów.
Rozwiązanie bazuje na predefiniowanym formacie danych wejściowych, który obejmuje nazwę produktu oraz jego opis. Narzędzie odczytuje te dane i przesyła je za pośrednictwem API do modelu AI, który analizuje kontekst i dopasowuje produkt do odpowiedniej kategorii. Otrzymana odpowiedź jest następnie zapisywana w dedykowanej kolumnie „AI Kategoria” w wyjściowym pliku CSV. Dzięki temu cały proces odbywa się masowo, bez ingerencji administratora.
Kluczowe korzyści:
- Automatyzacja: zastąpienie czasochłonnej, ręcznej pracy natychmiastowym procesem masowym.
- Spójność danych: jednolite przypisywanie kategorii nawet przy bardzo dużej liczbie produktów, co eliminuje chaos w katalogu.
- Oszczędność czasu i precyzja: znacząca redukcja błędów ludzkich oraz odciążenie zespołu od powtarzalnych zadań.
- Wykrywanie luk w strukturze: możliwość automatycznego oznaczania nowych typów produktów (np. tagiem
NEW_...), których aktualnie brakuje na zdefiniowanej liście kategorii.
Przykład konkretnego kodu Python, którego używam:
import pandas as pd
import json
import time
import re
from openai import OpenAI
from typing import List, Dict, Any
# --- Konfigurácia súborov a API ---
# POZNÁMKA: Tieto cesty sú špecifické pre Váš lokálny systém.
# Cesta k vstupným dátam
input_csv_file = r'C:\Users\marti\Desktop\py\rentabe\kategorizacia\data.csv'
# Cesta k výstupnému súboru
output_csv_file = r'C:\Users\marti\Desktop\py\rentabe\kategorizacia\output.csv'
# DeepSeek API konfigurácia
DEEPSEEK_API_KEY = '###'
DEEPSEEK_API_ENDPOINT = "https://api.deepseek.com/v1"
DEEPSEEK_MODEL = 'deepseek-chat'
# Zoznam preddefinovaných kategórií
# **DÔLEŽITÉ:** TENTO ZOZNAM NAHRAĎTE VAŠIM KOMPLETNÝM ZOZNAMOM KATEGÓRIÍ.
PREDEFINED_CATEGORIES = [
"Bicykle",
"Elektrobicykle",
"Detské bicykle",
"Aerifikatory",
"Aku viazače armatúr",
"Auto-Moto",
"Bagre",
"Betónovanie",
"Brúsky",
"Bubnové kosačky",
"Búracie kladivá",
"Čeluste",
"Čerpadlá",
"Chlapčenské kostýmy",
"Čistiace stroje na podlahu",
"Dámske kostýmy",
"Debnenie",
"Detektory kovov",
"Diamantové rezné kotúče",
"Dievčenské kostýmy",
"Drážkovacie (ryhovacie) frézy",
"Drviče a štiepkovače",
"Drviče konárov",
"Dumpery",
"Elektrické fúriky",
"Elektrické kladivá",
"Elektrické ohrievače",
"Elektrocentrály",
"Frézy",
"Fúkače, rosiče a vysávače lístia",
"Gola sady",
"Hadice",
"Hladičky na betón a príslušenstvo",
"Hoblíky",
"Hoblovačky",
"Jadrové vŕtačky",
"Jadrové vrtáky",
"Kalibrátory",
"Kalové čerpadlá",
"Kladivá",
"Kliešte",
"Kliešte na obrubníky",
"Klincovačky",
"Kombinované kladivá",
"Kompresory",
"Kontajnery",
"Kosačky",
"Kostýmy",
"Kotúče", # Kategória, ktorá sa môže prekrývať s "Brúsky"
"Krovinorezy",
"Kultivátory a rotavátory",
"Lamačky na zámkové dlažby",
"Laserové a stavebné merače",
"Lešenie",
"Lisovacie čeľuste",
"Lisovacie kliešte",
"Lisovacie zariadenia a príslušenstvo",
"Magnetické vŕtačky",
"Malotraktory",
"Maskoti",
"Miešačky",
"Miešadlá",
"Minibagre",
"Mininakladače",
"Motorové píly",
"Mulčovače",
"Nabíjačky",
"Naftové ohrievače",
"Nafukovacie atrakcie",
"Nafukovacie hrady",
"Nakladače",
"Nitovačky",
"Niveláky (nivelačné prístroje)",
"Nožnice",
"Nožnice na živý plot",
"Obleky"
"Odvlhčovače vzduchu a muriva",
"Ohrievače",
"Ohýbačky",
"Ohýbačky hákov",
"Omietacie stroje",
"Paletové vozíky",
"Pánske kostýmy",
"Pásové dopravníky",
"Pásové dumpery",
"Pieskovačky",
"Píly",
"Plošiny",
"Plotostrihy a nožnice na živý plot",
"Plynové ohrievače",
"Pôdne frézy",
"Pokosové píly",
"Ponorné vibrátory na betón",
"Presievače (osievačky) zeminy",
"Prevzdušňovače",
"Prísavky",
"Príslušenstvo",
"Prívesne vozíky",
"Rebríky",
"Rezačky",
"Rudla (rudlíky)",
"Rýpadlá",
"Sanačné brúsky",
"Schodiskové veže",
"Sekáče",
"Sekacie kladivá",
"Sklzy na suť a stavebný odpad",
"Skrutkovače a uťahováky",
"Spätné frézy",
"Sponkovačky",
"Stany",
"Stavebné výťahy",
"Štiepačky a kálačky na drevo",
"Stojky",
"Striekacie pištole",
"Stroje a náradie", # Kľúčová kategória
"Stroje na čistenie a príslušenstvo",
"Stroje na potery",
"Svietidlá a osvetlenie",
"Šaty"
"Svadobné šaty"
"Spoločenské šaty"
"Tažká technika",
"Tepovače",
"Traktorové kosačky",
"Uťahováky",
"Valce",
"Ventilátory",
"Vertikulátory",
"Vibračné dosky (žaby)",
"Vibračné laty (lišty)",
"Vibračné nohy",
"Vibračné stroje",
"Vibračné válce",
"Vŕtacie kladivá",
"Vŕtačky",
"Vrtáky",
"Vykružovačky",
"Vysávače",
"Vysokotlakové čističe (vapky)",
"Vysokozdvižné vozíky",
"Záhradna technika", # Kľúčová kategória
"Záhradné valce na trávu",
"Zakladače trávnika",
"Závitorezy",
"Zdviháky",
"Zemné vrtáky",
"Žeriavy",
"Zlupovače trávnika",
"Zmrazovače potrubia",
"Zváračky",
"Zváračky CO2",
"Zváračky TIG"
]
PREDEFINED_CATEGORIES_SET = set(PREDEFINED_CATEGORIES)
# Inicializácia DeepSeek synchrónneho klienta
client = OpenAI(
api_key=DEEPSEEK_API_KEY,
base_url=DEEPSEEK_API_ENDPOINT,
)
# --- Funkcia na čistenie HTML ---
def clean_html(raw_html: Any) -> str:
"""Odstráni HTML značky z textu a zabezpečí, že vstup je string."""
clean_re = re.compile('<.*?>')
# Prevod na string a odstránenie HTML
clean_text = re.sub(clean_re, '', str(raw_html))
# Odstránenie prebytočných medzier (vrátane pevných medzier U+00A0) a prázdneho riadka
return ' '.join(clean_text.split())
# --- Funkcia pre volanie DeepSeek API a kategorizáciu ---
def get_product_categories(product_nazov: str, product_popis: str, predefined_categories: List[str]) -> List[str]:
"""
Volá DeepSeek API pre kategorizáciu produktu na základe názvu a popisu.
Vráti list (pole) kategórií, spracovaný podľa pravidiel.
"""
cleaned_nazov = clean_html(product_nazov)
cleaned_popis = clean_html(product_popis)
categories_string = ', '.join(predefined_categories)
# Vylepšený Prompt pre DeepSeek API
prompt_template = f"""
Na základe Názvu a Popisu produktu priraď produkt do kategórií. Vráť odpoveď výhradne vo formáte JSON.
Vstupné údaje:
Názov produktu: {cleaned_nazov}
Popis produktu: {cleaned_popis}
Zoznam platných kategórií (vyberaj prednostne z tohto zoznamu):
{categories_string}
Pravidlá kategorizácie:
1. Vráť odpoveď výhradne vo formáte JSON s jediným kľúčom: "kategorie", ktorého hodnota je pole (list) stringov. Nezahŕňaj žiadny iný text ani vysvetlenia.
2. Priraď produkt do VŠETKÝCH relevantných a logických kategórií z dodaného zoznamu. Zaradenie musí dávať 100% zmysel.
3. Ak sa produkt hodí do kategórií "Stroje a náradie" alebo "Záhradna technika", priraď aj tieto (ak sú relevantné). Väčšina produktov by mala byť zaradená aspoň do jednej z nich.
4. Ak sa produkt NEHODÍ do žiadnej z preddefinovaných kategórií, vytvor JEDNU novú, čo najviac deskriptívnu a špecifickú kategóriu, a uveď ju BEZ predpony. Python kód predponu NEW_ pridá.
5. Výsledkom musí byť vždy zoznam kategórií (pole stringov), ktorý obsahuje aspoň jeden prvok.
Príklad JSON výstupu: {{"kategorie": ["Vŕtacie kladivá", "Stroje a náradie"]}}
"""
api_response_content = ""
try:
# Volanie API
chat_completion = client.chat.completions.create(
model=DEEPSEEK_MODEL,
messages=[
{"role": "system", "content": "You are a helpful assistant that provides precise JSON output based on product categorization rules."},
{"role": "user", "content": prompt_template}
],
temperature=0.0,
response_format={"type": "json_object"}
)
api_response_content = chat_completion.choices[0].message.content
# Očistenie od prípadného markdown obalu "```json ... ```"
cleaned_response = api_response_content.strip().lstrip("```json").rstrip("```")
parsed_response = json.loads(cleaned_response)
# Získanie vrátených kategórií ako listu
raw_categories = parsed_response.get('kategorie', [])
final_categories = []
is_brusky_present = "Brúsky" in raw_categories
for cat in raw_categories:
# 1. Exkluzívna logika: Ak je Brúsky, nepridávaj Kotúče/Diamantové
if is_brusky_present and cat in ["Kotúče", "Diamantové rezné kotúče", "Brúsny kotúč", "Brúsne kotúče"]:
# Preskočiť kotúčovú kategóriu, ak už je Brúska
continue
# 2. Logika pre NEW_ kategórie (kategórie, ktoré nie sú v preddefinovanom zozname)
if cat not in PREDEFINED_CATEGORIES_SET:
# Pridaj predponu NEW_
final_categories.append(f"NEW_{cat}")
else:
# Kategória je platná a je v zozname
final_categories.append(cat)
# Zabezpečíme, že sa vráti aspoň jeden prvok v prípade, že by sa všetky vyfiltrovali
if not final_categories and raw_categories:
# Ak by došlo k filtru na 0, vrátime aspoň prvé (pôvodne vrátené) kategórie s NEW_ predponou
return [f"NEW_Zaradenie zlyhalo: {raw_categories[0]}"]
if not final_categories:
return ["Chyba kategorizácie - prázdny výstup"]
return sorted(list(set(final_categories))) # Odstráni duplicity a zoradí pre konzistenciu
except json.JSONDecodeError as e:
print(f"Chyba pri parsovaní JSON odpovede: {e}")
print(f"Nespracovaná odpoveď API: {api_response_content}")
return ["Chyba spracovania JSON"]
except Exception as e:
print(f"Chyba pri volaní DeepSeek API: {e}")
# Odporúča sa krátka pauza pri API chybách
time.sleep(1)
return ["Chyba API"]
# --- Hlavný synchrónny skript na spracovanie CSV ---
def process_product_feed(input_file: str, output_file: str, batch_size: int = 10):
# VÝSTUPNÝ STĹPEC
OUTPUT_CATEGORY_COLUMN_NAME = 'AI Kategoria'
try:
# 1. Načítanie súboru - Skúšame čiarku a potom tabulátor pre najlepšiu kompatibilitu
try:
# Skúška 1: Čiarka
df = pd.read_csv(input_file, sep=',', encoding='utf-8')
print("Načítané s oddeľovačom: čiarka (',')")
except Exception:
# Skúška 2: Tabulátor
df = pd.read_csv(input_file, sep='\t', encoding='utf-8')
print("Načítané s oddeľovačom: tabulátor ('\\t')")
print(f"Načítaných {len(df)} dátových riadkov z {input_file}")
# 2. Vylepšená manipulácia s hlavičkou stĺpca
df.columns = df.columns.str.strip()
# Overenie existencie stĺpcov
NAZOV_COL = next((col for col in df.columns if col.lower() == 'nazov'), None)
POPIS_COL = next((col for col in df.columns if col.lower() == 'popis'), None)
if not NAZOV_COL or not POPIS_COL:
print(f"Chyba: V hlavičke CSV nebol nájdený stĺpec 'Nazov' alebo 'Popis'.")
print(f"Načítaná hlavička stĺpcov: {list(df.columns)}")
return # Ukončí spracovanie s chybou
# 3. Pridanie nového stĺpca pre AI kategóriu
if OUTPUT_CATEGORY_COLUMN_NAME not in df.columns:
df[OUTPUT_CATEGORY_COLUMN_NAME] = ''
# 4. Hlavný cyklus spracovania
for i, row in df.iterrows():
# Ak už je kategória vyplnená, preskoč riadok (optimalizácia pri reštarte)
current_category = str(row[OUTPUT_CATEGORY_COLUMN_NAME]).strip()
# Preskočenie iba ak je hodnota platná (nie je to chybový stav)
if current_category and current_category not in ("Chyba spracovania JSON", "Chyba API", "Chyba kategorizácie - prázdny výstup"):
print(f"Riadok [{i+1}/{len(df)}] bol už spracovaný. Preskakujem.")
continue
product_nazov = row[NAZOV_COL]
product_popis = row[POPIS_COL]
nazov_na_tlac = str(product_nazov)
print(f"Spracúvam riadok [{i+1}/{len(df)}]: Názov: {nazov_na_tlac[:50].strip()}...")
# Volanie funkcie pre kategorizáciu
new_categories = get_product_categories(product_nazov, product_popis, PREDEFINED_CATEGORIES)
# Konverzia listu kategórií na string oddelený čiarkou a medzerou
category_string = ", ".join(new_categories)
# Zápis výsledku do nového stĺpca
df.at[i, OUTPUT_CATEGORY_COLUMN_NAME] = category_string
# Ukladanie každých 'batch_size' riadkov (po 10 spracovaných)
if (i + 1) % batch_size == 0:
# Používame čiarku ako oddeľovač pre výstup, je univerzálnejšia
df.to_csv(output_file, index=False, encoding='utf-8', sep=',')
print(f"--- Uložených {i + 1} riadkov do {output_file} (záloha) ---")
time.sleep(0.5)
# Uloženie zvyšných riadkov po dokončení cyklu
df.to_csv(output_file, index=False, encoding='utf-8', sep=',')
print(f"Spracovanie dokončené. Všetky riadky uložené do {output_file}")
except FileNotFoundError:
print(f"Chyba: Vstupný súbor '{input_file}' nebol nájdený. Skontrolujte cestu.")
except Exception as e:
print(f"Vyskytla sa neočakávaná chyba: {e}")
# Spustenie spracovania
if __name__ == "__main__":