Spaces:
Running
Running
| # web_indexer_universal_v7.py | |
| # JAVÍTVA: A hitelesítő adatok és a konfiguráció a GitHub Actions Secrets-ből érkeznek. | |
| # Robusztusabb logikával, a backendből importált funkciók helyett. | |
| import os | |
| import time | |
| import traceback | |
| import requests | |
| from bs4 import BeautifulSoup | |
| from urllib.parse import urljoin, urlparse | |
| from collections import deque | |
| from elasticsearch import Elasticsearch, helpers, exceptions as es_exceptions | |
| import sys | |
| import warnings | |
| # === ANSI Színkódok (konzol loggoláshoz) === | |
| GREEN = '\033[92m' | |
| YELLOW = '\033[93m' | |
| RED = '\033[91m' | |
| RESET = '\033[0m' | |
| BLUE = '\033[94m' | |
| CYAN = '\033[96m' | |
| MAGENTA = '\033[95m' | |
| # --- LLM és egyéb könyvtárak ellenőrzése és importálása --- | |
| try: | |
| import torch | |
| TORCH_AVAILABLE = True | |
| except ImportError: | |
| TORCH_AVAILABLE = False | |
| print(f"{RED}FIGYELEM: Torch nincs telepítve. Egyes funkciók nem működnek.{RESET}") | |
| try: | |
| import together | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| # <<< JAVÍTVA: A together_api_key betöltése a környezeti változókból | |
| together_api_key = os.getenv("TOGETHER_API_KEY") | |
| if not together_api_key: | |
| print(f"{RED}Hiba: TOGETHER_API_KEY környezeti változó nincs beállítva. LLM funkciók nem működnek.{RESET}") | |
| together_client = None | |
| else: | |
| together_client = together.Together(api_key=together_api_key) | |
| print(f"{GREEN}Together AI kliens inicializálva.{RESET}") | |
| except ImportError: | |
| print(f"{YELLOW}Figyelem: together könyvtár nincs telepítve. LLM funkciók nem fognak működni.{RESET}") | |
| together_client = None | |
| except Exception as e: | |
| print(f"{RED}Hiba LLM backend inicializálásakor: {e}{RESET}") | |
| together_client = None | |
| # ... (a többi import változatlan) | |
| try: | |
| import tiktoken | |
| tiktoken_encoder = tiktoken.get_encoding("cl100k_base") | |
| TIKTOKEN_AVAILABLE = True | |
| except ImportError: | |
| TIKTOKEN_AVAILABLE = False | |
| print(f"{YELLOW}Figyelem: tiktoken nincs telepítve. Token darabolás a beállított karakterszámmal történik.{RESET}") | |
| try: | |
| import nltk | |
| try: | |
| nltk.data.find('tokenizers/punkt') | |
| except LookupError: | |
| print(f"{CYAN}NLTK 'punkt' letöltése...{RESET}"); | |
| nltk.download('punkt', quiet=True) | |
| NLTK_AVAILABLE = True | |
| except ImportError: | |
| NLTK_AVAILABLE = False | |
| print(f"{RED}HIBA: 'nltk' nincs telepítve! Szövegtördelés nem lesz pontos.{RESET}") | |
| try: | |
| from sentence_transformers import SentenceTransformer | |
| SENTENCE_TRANSFORMER_AVAILABLE = True | |
| except ImportError: | |
| SENTENCE_TRANSFORMER_AVAILABLE = False | |
| print(f"{RED}HIBA: 'sentence-transformers' nincs telepítve! Embedding nem működik.{RESET}") | |
| try: | |
| sys.stdout.reconfigure(encoding='utf-8') | |
| sys.stderr.reconfigure(encoding='utf-8') | |
| except AttributeError: | |
| print(f"{YELLOW}Figyelem: Kódolás beállítása nem sikerült.{RESET}") | |
| # --- Konfiguráció --- | |
| # <<< JAVÍTVA: A hitelesítő adatok betöltése környezeti változókból | |
| # Ezeket a GitHub Actions Secrets-ben kell beállítanod! | |
| ES_CLOUD_ID = os.getenv("ES_CLOUD_ID") | |
| ES_API_KEY = os.getenv("ES_API_KEY") | |
| # A TOGETHER_API_KEY már korábban betöltésre került | |
| START_URL = "https://www.dunaelektronika.com/" | |
| TARGET_DOMAIN = "dunaelektronika.com" | |
| MAX_DEPTH = 2 | |
| REQUEST_DELAY = 1 | |
| USER_AGENT = "MyPythonCrawler/1.0 (+http://example.com/botinfo)" | |
| VECTOR_INDEX_NAME = "dunawebindexai" | |
| SYNONYM_FILE_PATH_IN_ES_CONFIG = "analysis/synonyms_hu.txt" | |
| BATCH_SIZE = 50 | |
| ES_CLIENT_TIMEOUT = 120 | |
| EMBEDDING_MODEL_NAME = 'sentence-transformers/paraphrase-multilingual-mpnet-base-v2' | |
| embedding_model = None | |
| EMBEDDING_DIM = None | |
| device = 'cpu' | |
| CHUNK_SIZE_TOKENS = 500 | |
| CHUNK_OVERLAP_TOKENS = 50 | |
| MIN_CHUNK_SIZE_CHARS = 50 | |
| DEBUG_MODE = True | |
| LLM_MODEL_NAME = "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free" | |
| LLM_CHUNK_MODEL = "mistralai/Mixtral-8x7B-Instruct-v0.1" | |
| # --- LLM HÁTTÉR FUNKCIÓK --- | |
| # ... (ez a rész változatlan) | |
| def generate_categories_with_llm(llm_client, soup, text): | |
| """ | |
| Kategóriát generál HTML menüből/címből, vagy LLM-mel, ha az előző nem sikerül. | |
| """ | |
| # Előre definiált kategórialista, hogy az LLM mindig pontosan egyező kategóriát adjon. | |
| category_list = ['IT biztonsági szolgáltatások', 'szolgáltatások', 'hardver', 'szoftver', 'hírek', | |
| 'audiovizuális konferenciatechnika'] | |
| # 1. Keresés HTML menüpontok/útvonalak alapján (breadcrumb) | |
| try: | |
| breadcrumb = soup.find('nav', class_='breadcrumb') | |
| if breadcrumb: | |
| categories = [li.get_text(strip=True) for li in breadcrumb.find_all('li')] | |
| if categories: | |
| final_category_from_html = categories[-1] | |
| # Ezt a kategóriát is egyeztesse a listával | |
| for cat in category_list: | |
| if cat.lower() in final_category_from_html.lower(): | |
| print(f"{GREEN} -> Kategória a breadcrumb alapján: '{cat}'{RESET}") | |
| return [cat] | |
| except Exception as e: | |
| print(f"{YELLOW} Figyelem: Hiba a breadcrumb feldolgozásakor: {e}{RESET}") | |
| # 2. Keresés <h1> cím alapján | |
| try: | |
| h1_tag = soup.find('h1') | |
| if h1_tag and h1_tag.get_text(strip=True): | |
| h1_text = h1_tag.get_text(strip=True) | |
| for cat in category_list: | |
| if cat.lower() in h1_text.lower(): | |
| print(f"{GREEN} -> Kategória a H1 cím alapján: '{cat}'{RESET}") | |
| return [cat] | |
| except Exception as e: | |
| print(f"{YELLOW} Figyelem: Hiba a H1 cím feldolgozásakor: {e}{RESET}") | |
| # 3. LLM hívás, ha semmi más nem működik | |
| if not llm_client: return ['egyéb'] | |
| try: | |
| categories_text = ", ".join([f"'{cat}'" for cat in category_list]) | |
| prompt = f"""Adott egy weboldal szövege. Adj meg egyetlen, rövid kategóriát a következő listából, ami a legjobban jellemzi a tartalmát. A válaszodban csak a kategória szerepeljen, más szöveg, magyarázat, vagy írásjelek nélkül. | |
| Lehetséges kategóriák: {categories_text} | |
| Szöveg: {text[:1000]} | |
| Kategória:""" | |
| response = llm_client.chat.completions.create(model=LLM_CHUNK_MODEL, | |
| messages=[{"role": "user", "content": prompt}], temperature=0.1, | |
| max_tokens=30) | |
| if response and response.choices: | |
| category = response.choices[0].message.content.strip().replace("'", "").replace("`", "") | |
| for cat in category_list: | |
| if cat.lower() in category.lower(): | |
| print(f"{GREEN} -> Kategória LLM generálás alapján: '{cat}'{RESET}") | |
| return [cat] | |
| print( | |
| f"{YELLOW} -> Az LLM nem talált megfelelő kategóriát a listán. 'egyéb' kategória használata.{RESET}") | |
| return ['egyéb'] | |
| else: | |
| return ["egyéb"] | |
| except Exception as e: | |
| print(f"{RED}Hiba LLM kategorizáláskor: {e}{RESET}") | |
| return ['egyéb'] | |
| def generate_summary_with_llm(llm_client, text): | |
| """ | |
| Összefoglalást generál a szöveg első feléből egy LLM segítségével. | |
| """ | |
| if not llm_client: return text[:300] + "..." | |
| try: | |
| prompt = f"""Készíts egy rövid, de informatív összefoglalót a következő szövegről. A lényeges pontokat emeld ki, de ne lépd túl a 200 szó terjedelmet. | |
| Szöveg: {text} | |
| Összefoglalás:""" | |
| # Csak a szöveg első 4000 tokenjét használjuk, hogy elkerüljük a token limitet | |
| text_for_llm = text[:4000] | |
| response = llm_client.chat.completions.create(model=LLM_CHUNK_MODEL, | |
| messages=[{"role": "user", "content": prompt}], temperature=0.5, | |
| max_tokens=500) | |
| if response and response.choices: | |
| summary = response.choices[0].message.content.strip() | |
| print(f"{GREEN} -> Sikeres LLM összefoglalás generálás.{RESET}") | |
| return summary | |
| else: | |
| return text[:300] + "..." | |
| except Exception as e: | |
| print(f"{RED}Hiba LLM összefoglaláskor: {e}{RESET}") | |
| return text[:300] + "..." # Visszaesés a manuális csonkolásra hiba esetén | |
| def chunk_text_by_tokens(text, chunk_size, chunk_overlap): | |
| """ | |
| Szöveg feldarabolása tokenek szerint, átfedéssel. | |
| """ | |
| if not TIKTOKEN_AVAILABLE or not NLTK_AVAILABLE: | |
| # Fallback a karakterszám alapú darabolásra, ha a tokenizáló nincs telepítve | |
| print(f"{YELLOW}Figyelmeztetés: Tiktoken/NLTK hiányzik. Karakterszám alapú darabolás.{RESET}") | |
| # Egyszerű karakter alapú tördelés | |
| chunks = [] | |
| start = 0 | |
| while start < len(text): | |
| end = start + chunk_size | |
| chunks.append(text[start:end]) | |
| start += chunk_size - chunk_overlap | |
| return chunks | |
| tokens = tiktoken_encoder.encode(text) | |
| chunks = [] | |
| start = 0 | |
| while start < len(tokens): | |
| end = start + chunk_size | |
| chunk_tokens = tokens[start:end] | |
| chunks.append(tiktoken_encoder.decode(chunk_tokens)) | |
| start += chunk_size - chunk_overlap | |
| return chunks | |
| # --- Modellek és Eszközök Inicializálása --- | |
| # ... (ez a rész változatlan) | |
| def load_embedding_model(): | |
| global embedding_model, EMBEDDING_DIM, device | |
| if not TORCH_AVAILABLE or not SENTENCE_TRANSFORMER_AVAILABLE: | |
| EMBEDDING_DIM = 768 | |
| device = 'cpu' | |
| print(f"{RED}Hiba: PyTorch vagy SentenceTransformer nincs telepítve.{RESET}") | |
| return None, EMBEDDING_DIM, device | |
| if embedding_model and EMBEDDING_DIM: | |
| return embedding_model, EMBEDDING_DIM, device | |
| print(f"\n'{EMBEDDING_MODEL_NAME}' embedding modell betöltése (SentenceTransformer)...") | |
| try: | |
| current_device = 'cuda' if torch.cuda.is_available() else 'cpu' | |
| model = SentenceTransformer(EMBEDDING_MODEL_NAME, device=current_device) | |
| print(f"ST modell betöltve, használt eszköz: {model.device}") | |
| dim = model.get_sentence_embedding_dimension() | |
| if not dim: raise ValueError("Nem sikerült meghatározni az embedding dimenziót.") | |
| embedding_model = model | |
| EMBEDDING_DIM = dim | |
| device = current_device | |
| return embedding_model, EMBEDDING_DIM, device | |
| except Exception as e: | |
| print(f"{RED}Hiba embedding modell betöltésekor: {e}{RESET}") | |
| traceback.print_exc() | |
| embedding_model = None | |
| EMBEDDING_DIM = 768 | |
| device = 'cpu' | |
| return None, EMBEDDING_DIM, device | |
| embedding_model, EMBEDDING_DIM, device = load_embedding_model() | |
| # === Index Beállítások & Mapping === | |
| # ... (ez a rész változatlan) | |
| INDEX_SETTINGS_SEPARATE_ANALYZER = { | |
| "analysis": { | |
| "filter": { | |
| "hungarian_stop": {"type": "stop", "stopwords": "_hungarian_"}, | |
| "hungarian_stemmer": {"type": "stemmer", "language": "hungarian"}, | |
| "synonym_filter": {"type": "synonym_graph", "synonyms_path": SYNONYM_FILE_PATH_IN_ES_CONFIG, | |
| "updateable": True} | |
| }, | |
| "analyzer": { | |
| "hungarian_indexing_analyzer": {"tokenizer": "standard", | |
| "filter": ["lowercase", "hungarian_stop", "hungarian_stemmer"]}, | |
| "hungarian_search_analyzer": {"tokenizer": "standard", | |
| "filter": ["lowercase", "hungarian_stop", "synonym_filter", | |
| "hungarian_stemmer"]} | |
| } | |
| } | |
| } | |
| INDEX_MAPPINGS_WEB = { | |
| "properties": { | |
| "text_content": {"type": "text", "analyzer": "hungarian_indexing_analyzer", | |
| "search_analyzer": "hungarian_search_analyzer"}, | |
| "embedding": {"type": "dense_vector", "dims": EMBEDDING_DIM, "index": True, "similarity": "cosine"}, | |
| "source_origin": {"type": "keyword"}, | |
| "source_url": {"type": "keyword"}, | |
| "source_type": {"type": "keyword"}, | |
| "category": {"type": "keyword"}, | |
| "heading": {"type": "text", "analyzer": "hungarian_indexing_analyzer", | |
| "search_analyzer": "hungarian_search_analyzer"}, | |
| "summary": {"type": "text", "analyzer": "hungarian_indexing_analyzer", | |
| "search_analyzer": "hungarian_search_analyzer"} | |
| } | |
| } | |
| # --- Segédfüggvények --- | |
| # <<< JAVÍTVA: A függvény most már a környezeti változókat használja | |
| def initialize_es_client(): | |
| if DEBUG_MODE: print("\nKapcsolódás az Elasticsearch-hez a GitHub Secrets adatokkal...") | |
| # Ellenőrizzük, hogy a szükséges környezeti változók be vannak-e állítva | |
| if not ES_CLOUD_ID: | |
| print(f"{RED}Hiba: ES_CLOUD_ID környezeti változó hiányzik! Ezt a GitHub Secrets-ben kell beállítani.{RESET}") | |
| return None | |
| if not ES_API_KEY: | |
| print(f"{RED}Hiba: ES_API_KEY környezeti változó hiányzik! Ezt a GitHub Secrets-ben kell beállítani.{RESET}") | |
| return None | |
| client = None | |
| try: | |
| # A kliens inicializálása cloud_id és api_key segítségével | |
| client = Elasticsearch( | |
| cloud_id=ES_CLOUD_ID, | |
| api_key=ES_API_KEY, | |
| request_timeout=ES_CLIENT_TIMEOUT | |
| ) | |
| if not client.ping(): | |
| raise ConnectionError("Nem sikerült pingelni az Elasticsearch-t.") | |
| if DEBUG_MODE: print(f"{GREEN}Sikeres Elasticsearch kapcsolat!{RESET}") | |
| return client | |
| except Exception as e: | |
| print(f"{RED}Hiba az Elasticsearch kapcsolódás során: {e}{RESET}") | |
| traceback.print_exc() | |
| return None | |
| # ... (a többi segédfüggvény változatlan) | |
| def get_embedding(text): | |
| if not embedding_model: return None | |
| if not text or not isinstance(text, str): return None | |
| try: | |
| vector = embedding_model.encode(text, normalize_embeddings=True) | |
| return vector.tolist() | |
| except Exception as e: | |
| print(f"{RED}Hiba embedding közben: {e}{RESET}") | |
| return None | |
| def create_es_index(client, index_name, index_settings, index_mappings): | |
| if not EMBEDDING_DIM: | |
| print(f"{RED}Hiba: Embed dim nincs.{RESET}") | |
| return False | |
| try: | |
| embedding_mapping = index_mappings.get("properties", {}).get("embedding", {}) | |
| if not embedding_mapping: raise KeyError("Az 'embedding' kulcs hiányzik a mapping 'properties'-ből!") | |
| if embedding_mapping.get("dims") != EMBEDDING_DIM: | |
| print(f"{YELLOW}FIGYELEM: Mapping dim != Modell dim. Mapping frissítése {EMBEDDING_DIM}-re.{RESET}") | |
| index_mappings["properties"]["embedding"]["dims"] = EMBEDDING_DIM | |
| except KeyError as e: | |
| print(f"{RED}Hiba: Mapping struktúra érvénytelen! Kulcs: {e}{RESET}") | |
| return False | |
| except Exception as e: | |
| print(f"{RED}Hiba: Mapping hiba! {e}{RESET}") | |
| return False | |
| if DEBUG_MODE: print(f"\nIndex check: '{index_name}'?") | |
| try: | |
| if not client.indices.exists(index=index_name): | |
| print(f"'{index_name}' létrehozása...") | |
| resp = client.indices.create(index=index_name, settings=index_settings, mappings=index_mappings, | |
| request_timeout=ES_CLIENT_TIMEOUT) | |
| if resp.get('acknowledged'): | |
| print(f"{GREEN}Index OK.{RESET}") | |
| time.sleep(2) | |
| return True | |
| else: | |
| print(f"{RED}Hiba: Create no ack.{RESET}") | |
| return False | |
| else: | |
| print(f"Index '{index_name}' már létezik.") | |
| return True | |
| except es_exceptions.RequestError as e: | |
| err_str = str(e).lower() | |
| if 'resource_already_exists_exception' in err_str: | |
| if DEBUG_MODE: print("Index már létezik (exception).") | |
| return True | |
| elif 'resource_not_found_exception' in err_str and ('synonyms_path' in err_str or ( | |
| SYNONYM_FILE_PATH_IN_ES_CONFIG and SYNONYM_FILE_PATH_IN_ES_CONFIG.split('/')[-1] in err_str)): | |
| print(f"{RED}!!! Hiba: Szinonima fájl nincs ES-ben: '{SYNONYM_FILE_PATH_IN_ES_CONFIG}'?{RESET}") | |
| else: | |
| print(f"{RED}!!! Hiba index create (RequestError): {e}{RESET}") | |
| return False | |
| except Exception as e: | |
| print(f"{RED}!!! Hiba index check/create: {e}{RESET}") | |
| traceback.print_exc() | |
| return False | |
| def extract_text_from_html(html_content): | |
| try: | |
| soup = BeautifulSoup(html_content, 'html.parser') | |
| for element in soup(["script", "style", "nav", "footer", "header", "aside", "form"]): | |
| if element: element.decompose() | |
| main_content = soup.find('main') or soup.find('article') or soup.body | |
| if main_content: | |
| text = main_content.get_text(separator='\n', strip=True) | |
| cleaned_text = "\n".join(line for line in text.splitlines() if line.strip()) | |
| return cleaned_text | |
| else: | |
| return "" | |
| except Exception as e: | |
| print(f"{RED}Hiba HTML elemzés: {e}{RESET}") | |
| return "" | |
| def extract_and_filter_links(soup, base_url, target_domain): | |
| links = set() | |
| try: | |
| for a_tag in soup.find_all('a', href=True): | |
| href = a_tag['href'].strip() | |
| if href and not href.startswith(('#', 'mailto:', 'javascript:')): | |
| full_url = urljoin(base_url, href) | |
| parsed_url = urlparse(full_url) | |
| if parsed_url.scheme in ['http', 'https'] and parsed_url.netloc == target_domain: | |
| normalized_url = parsed_url._replace(fragment="").geturl() | |
| links.add(normalized_url) | |
| except Exception as e: | |
| print(f"{RED}Hiba link kinyerés: {e}{RESET}") | |
| return links | |
| def crawl_and_index_website(start_url, max_depth, es_client, index_name): | |
| if not es_client: print(f"{RED}Hiba: ES kliens nincs init.{RESET}"); return 0 | |
| if not embedding_model: print(f"{RED}Hiba: Embedding modell nincs init.{RESET}"); return 0 | |
| try: | |
| import requests; | |
| from bs4 import BeautifulSoup; | |
| from urllib.parse import urljoin, \ | |
| urlparse; | |
| from collections import deque | |
| except ImportError: | |
| print(f"{RED}Hiba: Crawling könyvtárak hiányoznak.{RESET}"); | |
| return 0 | |
| global together_client, BATCH_SIZE, CHUNK_SIZE_TOKENS, CHUNK_OVERLAP_TOKENS, MIN_CHUNK_SIZE_CHARS | |
| visited_urls = set() | |
| urls_to_visit = deque([(start_url, 0)]) | |
| bulk_actions = [] | |
| total_prepared = 0; | |
| total_indexed = 0 | |
| try: | |
| target_domain = urlparse(start_url).netloc; | |
| except Exception as url_err: | |
| print(f"{RED}Hiba: Start URL feldolgozása ({start_url}): {url_err}{RESET}"); | |
| return 0 | |
| print(f"Web crawling indítása: {start_url} (Max mélység: {max_depth}, Cél: {target_domain})") | |
| while urls_to_visit: | |
| current_url = None | |
| try: | |
| current_url, current_depth = urls_to_visit.popleft() | |
| try: | |
| parsed_check = urlparse(current_url); | |
| except Exception as parse_err: | |
| print(f" {YELLOW}-> Hibás URL formátum, kihagyva: {current_url}{RESET}"); | |
| continue | |
| if current_url in visited_urls: continue | |
| if current_depth > max_depth: continue | |
| print(f"\n--- Feldolgozás (Mélység: {current_depth}): {current_url} ---") | |
| visited_urls.add(current_url) | |
| html_content = None | |
| try: | |
| headers = {'User-Agent': USER_AGENT} | |
| response = requests.get(current_url, headers=headers, timeout=15) | |
| response.raise_for_status() | |
| content_type = response.headers.get('content-type', '').lower() | |
| if 'text/html' not in content_type: print( | |
| f" {YELLOW}-> Nem HTML ({content_type}), kihagyva.{RESET}"); continue | |
| html_content = response.content | |
| except requests.exceptions.RequestException as req_err: | |
| print(f" {RED}!!! Hiba letöltés: {req_err}{RESET}"); | |
| continue | |
| except Exception as fetch_err: | |
| print(f" {RED}!!! Váratlan hiba letöltés: {fetch_err}{RESET}"); | |
| continue | |
| if DEBUG_MODE: print(" HTML elemzése és kategorizálása...") | |
| soup = BeautifulSoup(html_content, 'html.parser') | |
| page_text = extract_text_from_html(html_content) | |
| if not page_text or len(page_text) < MIN_CHUNK_SIZE_CHARS: | |
| print(f" {YELLOW}-> Nem sikerült szöveget kinyerni vagy túl rövid.{RESET}"); | |
| continue | |
| # --- ÚJ, JAVÍTOTT LOGIKA --- | |
| # 1. Szövegtördelés token alapján | |
| final_chunks = chunk_text_by_tokens(page_text, CHUNK_SIZE_TOKENS, CHUNK_OVERLAP_TOKENS) | |
| chunk_type = "token_chunking" | |
| # 2. LLM-alapú kategorizálás (az első 1000 karakter alapján) | |
| url_category = generate_categories_with_llm(together_client, soup, page_text[:1000])[0] | |
| # 3. LLM-alapú összefoglalás a teljes oldalhoz, ha van LLM kliens | |
| page_summary = generate_summary_with_llm(together_client, page_text) | |
| # --- INDEXELÉS ELŐKÉSZÍTÉSE --- | |
| if final_chunks: | |
| print( | |
| f"{GREEN} Indexelendő chunkok: {len(final_chunks)} (Típus: {chunk_type}, Kategória: {url_category}){RESET}") | |
| else: | |
| print(f"{RED} Nincs indexelendő chunk ({chunk_type}).{RESET}"); | |
| continue | |
| if DEBUG_MODE: print(f" Chunkok indexelésének előkészítése...") | |
| page_chunk_count = 0 | |
| for chunk_text in final_chunks: | |
| element_vector = get_embedding(chunk_text) | |
| if element_vector: | |
| total_prepared += 1; | |
| page_chunk_count += 1 | |
| doc = {"text_content": chunk_text, "embedding": element_vector, "source_origin": "website", | |
| "source_url": current_url, "source_type": chunk_type, "category": url_category, | |
| "summary": page_summary} | |
| bulk_actions.append({"_index": index_name, "_source": doc}) | |
| if len(bulk_actions) >= BATCH_SIZE: | |
| if DEBUG_MODE: print(f" -> {len(bulk_actions)} web chunk indexelése (batch)...") | |
| try: | |
| success_count, errors = helpers.bulk(es_client, bulk_actions, raise_on_error=False, | |
| request_timeout=ES_CLIENT_TIMEOUT) | |
| total_indexed += success_count | |
| if errors: print(f"{RED}!!! Hiba web bulk: {len(errors)} sikertelen.{RESET}") | |
| except Exception as be: | |
| print(f"{RED}!!! Váratlan web bulk hiba: {be}{RESET}") | |
| finally: | |
| bulk_actions = [] | |
| print(f" Oldal ({current_url}) feldolgozása kész ({page_chunk_count} chunk indexelve).") | |
| if current_depth < max_depth: | |
| if DEBUG_MODE: print(" Linkek keresése...") | |
| try: | |
| soup_for_links = BeautifulSoup(html_content, 'html.parser') | |
| new_links = extract_and_filter_links(soup_for_links, current_url, target_domain) | |
| if DEBUG_MODE: print(f" Talált {len(new_links)} új, belső linket.") | |
| for link in new_links: | |
| if link not in visited_urls: | |
| if len(urls_to_visit) < 5000: | |
| urls_to_visit.append((link, current_depth + 1)) | |
| else: | |
| print( | |
| f"{YELLOW}Figyelmeztetés: A bejárási sor túl hosszú, új link kihagyva: {link}{RESET}") | |
| except Exception as link_err: | |
| print(f"{RED}!!! Hiba linkek kinyerése: {link_err}{RESET}") | |
| if DEBUG_MODE: print(f" Várakozás {REQUEST_DELAY} mp..."); time.sleep(REQUEST_DELAY) | |
| except KeyboardInterrupt: | |
| print("\nFolyamat megszakítva."); | |
| break | |
| except Exception as loop_err: | |
| print(f"{RED}!!! Hiba ciklusban ({current_url}): {loop_err}{RESET}"); | |
| traceback.print_exc(); | |
| time.sleep(5) | |
| if bulk_actions: | |
| if DEBUG_MODE: print(f" -> Maradék {len(bulk_actions)} web chunk indexelése...") | |
| try: | |
| success_count, errors = helpers.bulk(es_client, bulk_actions, raise_on_error=False, | |
| request_timeout=ES_CLIENT_TIMEOUT) | |
| total_indexed += success_count | |
| if errors: print(f"{RED}!!! Hiba maradék web bulk: {len(errors)} sikertelen.{RESET}") | |
| except Exception as be: | |
| print(f"{RED}!!! Maradék web bulk hiba: {be}{RESET}") | |
| print(f"\n--- Web Crawling és Indexelés Befejezve ---") | |
| print(f"Meglátogatott URL-ek: {len(visited_urls)}") | |
| print(f"Előkészített chunk: {total_prepared}") | |
| final_success = min(total_indexed, total_prepared) | |
| print(f"Sikeresen indexelt chunk: {final_success}") | |
| return final_success | |
| # ... (a main függvény változatlan) | |
| if __name__ == "__main__": | |
| print(f"----- Web Crawler és Indexelő Indítása a '{VECTOR_INDEX_NAME}' indexbe (LLM-alapú) -----") | |
| print(f"----- Cél URL: {START_URL} (Max mélység: {MAX_DEPTH}) -----") | |
| print("****** FIGYELEM ******") | |
| print(f"Ez a script létrehozza/használja a '{VECTOR_INDEX_NAME}' indexet.") | |
| print(f"Győződj meg róla, hogy a szinonima beállítások az ES-ben léteznek ehhez az indexhez.") | |
| print(f"{RED}Ha a '{VECTOR_INDEX_NAME}' index már létezik, TÖRÖLD manuálisan futtatás előtt!{RESET}") | |
| print("SZÜKSÉGES KÖNYVTÁRAK: requests, beautifulsoup4, nltk, tiktoken, together stb.") | |
| print("******------------******") | |
| if not TORCH_AVAILABLE: print(f"{RED}Hiba: PyTorch.{RESET}"); exit(1) | |
| if not SENTENCE_TRANSFORMER_AVAILABLE: print(f"{RED}Hiba: SentenceTransformer.{RESET}"); exit(1) | |
| if not embedding_model: print(f"{RED}Hiba: Embedding modell.{RESET}"); exit(1) | |
| if not EMBEDDING_DIM: print(f"{RED}Hiba: Embedding dim.{RESET}"); exit(1) | |
| try: | |
| import requests; | |
| from bs4 import BeautifulSoup; | |
| from urllib.parse import urljoin, \ | |
| urlparse; | |
| from collections import deque | |
| except ImportError: | |
| print(f"{RED}Hiba: Crawling könyvtárak.{RESET}"); | |
| exit(1) | |
| es_client = initialize_es_client() | |
| final_success_count = 0 | |
| index_ready = False | |
| if es_client: | |
| index_ready = create_es_index(client=es_client, index_name=VECTOR_INDEX_NAME, | |
| index_settings=INDEX_SETTINGS_SEPARATE_ANALYZER, | |
| index_mappings=INDEX_MAPPINGS_WEB) | |
| if index_ready: | |
| print(f"\nIndex '{VECTOR_INDEX_NAME}' kész. Web crawling és indexelés indítása...") | |
| final_success_count = crawl_and_index_website(START_URL, MAX_DEPTH, es_client, VECTOR_INDEX_NAME) | |
| else: | |
| print(f"{RED}Hiba: Index nem áll készen.{RESET}") | |
| else: | |
| print(f"{RED}Hiba: ES kliens nem elérhető.{RESET}") | |
| print("\n----- Feldolgozás Befejezve -----") | |
| if index_ready and final_success_count > 0: | |
| print( | |
| f"\n{GREEN}Crawling és indexelés sikeres. {final_success_count} chunk indexelve '{VECTOR_INDEX_NAME}'-be.{RESET}"); | |
| print(f"Ellenőrzés: GET /{VECTOR_INDEX_NAME}/_count"); | |
| print(f"\nFontos: A RAG scriptet módosítani kell, hogy '{VECTOR_INDEX_NAME}' indexben IS keressen.") | |
| elif index_ready and final_success_count == 0: | |
| print(f"{YELLOW}Crawling lefutott, de 0 chunk indexelve.{RESET}") | |
| elif not index_ready: | |
| print(f"{RED}Index nem jött létre.{RESET}") | |
| else: | |
| print(f"{RED}Folyamat hibával zárult.{RESET}") |