# Ten plik jest odpowiedzialny za scrapowanie danych ze strony. from langchain_community.document_loaders import SitemapLoader from bs4 import BeautifulSoup import re from langchain_chroma import Chroma from langchain_openai import OpenAIEmbeddings from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_core.documents import Document import requests from tqdm import tqdm def process_documents(docs: list[Document]) -> list[Document]: """ Przetwarza listę dokumentów, wyodrębniając treść i metadane z HTML. """ processed_docs = [] for doc in docs: soup = BeautifulSoup(doc.page_content, "lxml") # Wyodrębnienie głównej treści article = soup.find("article") if article: content = article.get_text(separator="\n", strip=True) else: content = soup.get_text(separator="\n", strip=True) # Wyodrębnienie metadanych metadata = doc.metadata.copy() # Kopiujemy istniejące metadane (np. source) # Title: Zgodnie z sugestią, tytuł jest pobierany tylko ze znacznika if soup.title: title_text = soup.title.get_text(strip=True) if title_text: metadata["title"] = title_text # Data publikacji # Published time: prefer meta[property=article:published_time], then <time>, then regex search pub_date_tag = soup.find("meta", property="article:published_time") if pub_date_tag and pub_date_tag.get("content"): metadata["published_time"] = pub_date_tag["content"] else: time_tag = soup.find("time") if time_tag and time_tag.get("datetime"): metadata["published_time"] = time_tag.get("datetime") elif time_tag and time_tag.get_text(strip=True): metadata["published_time"] = time_tag.get_text(strip=True) else: # Polish pages often have 'Opublikowano w dniu 8 marca 2011' as plain text text = soup.get_text(separator="\n", strip=True) m = re.search(r"Opublikowano(?: w dniu)?[:\s]+([0-9]{1,2}\s+\w+\s+\d{4})", text, re.IGNORECASE) if m: metadata["published_time"] = m.group(1) # Kategorie categories = [ tag["content"] for tag in soup.find_all("meta", property="article:section") if tag.get("content") ] if categories: metadata["categories"] = ", ".join(categories) # Słowa kluczowe (tagi) keywords = [ tag["content"] for tag in soup.find_all("meta", property="article:tag") if tag.get("content") ] if keywords: metadata["keywords"] = ", ".join(keywords) processed_docs.append(Document(page_content=content, metadata=metadata)) return processed_docs embedder=OpenAIEmbeddings(model="text-embedding-3-small", show_progress_bar=True) baza=Chroma(collection_name="szuflada", embedding_function=embedder, persist_directory="./szuflada") # --- DODANA SEKCJA --- # Czyszczenie istniejącej kolekcji przed dodaniem nowych danych # To zapewnia, że pracujemy na świeżych danych z metadanymi. print("Czyszczenie istniejącej kolekcji w bazie danych...") try: baza.delete_collection() print("Kolekcja została wyczyszczona.") # Po usunięciu kolekcji, musimy ponownie zainicjować obiekt Chroma baza=Chroma(collection_name="szuflada", embedding_function=embedder, persist_directory="./szuflada") except Exception as e: print(f"Nie można było wyczyścić kolekcji (może nie istniała): {e}") # --- KONIEC DODANEJ SEKCJI --- # --- Nowa logika ładowania danych --- print("Pobieranie i parsowanie mapy strony...") headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} sitemap_url = "https://mojaszuflada.pl/wp-sitemap.xml" docs = [] try: response = requests.get(sitemap_url, headers=headers) response.raise_for_status() sitemap_xml = response.text sitemap_soup = BeautifulSoup(sitemap_xml, "xml") urls = [loc.text for loc in sitemap_soup.find_all("loc")] sitemap_urls = [url for url in urls if url.endswith(".xml")] page_urls = [url for url in urls if not url.endswith(".xml")] for sub_sitemap_url in tqdm(sitemap_urls, desc="Parsowanie pod-map"): try: response = requests.get(sub_sitemap_url, headers=headers) response.raise_for_status() sub_sitemap_xml = response.text sub_sitemap_soup = BeautifulSoup(sub_sitemap_xml, "xml") page_urls.extend([loc.text for loc in sub_sitemap_soup.find_all("loc")]) except requests.RequestException as e: print(f"Pominięto pod-mapę {sub_sitemap_url}: {e}") print(f"Znaleziono {len(page_urls)} adresów URL do przetworzenia.") for url in tqdm(page_urls, desc="Pobieranie stron"): try: response = requests.get(url, headers=headers) response.raise_for_status() doc = Document( page_content=response.text, metadata={"source": url, "loc": url} ) docs.append(doc) except requests.RequestException as e: print(f"Pominięto stronę {url}: {e}") except requests.RequestException as e: print(f"Krytyczny błąd: Nie udało się pobrać głównej mapy strony: {e}") # docs will be empty and the script will exit gracefully later if not docs: print("Nie załadowano żadnych dokumentów. Zakończenie pracy.") exit() processed_docs = process_documents(docs) print("\nPrzykładowe metadane przetworzonych dokumentów (pierwsze 5):") for pd in processed_docs[:5]: print(pd.metadata) text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) chunks = text_splitter.split_documents(processed_docs) batch_size = 1000 # --- WALIDACJA METADANYCH DLA CHUNKÓW --- # Sprawdzamy, czy każdy chunk zawiera oczekiwane metadane (źródło, tytuł, data publikacji) required_meta_keys = ["source", "title", "published_time"] missing_counts = {k: 0 for k in required_meta_keys} for idx, chunk in enumerate(chunks): md = chunk.metadata or {} for k in required_meta_keys: if not md.get(k): missing_counts[k] += 1 print(f"Liczba chunków: {len(chunks)}") print("Braki metadanych (liczba chunków bez klucza/wartości):", missing_counts) print("Przykładowe metadane dla pierwszych 5 chunków:") for sample in chunks[:5]: print(sample.metadata) # --- KONIEC WALIDACJI --- for i in range(0, len(chunks), batch_size): baza.add_documents(documents=chunks[i:i + batch_size])