356 lines
17 KiB
Python
356 lines
17 KiB
Python
import time
|
||
import pandas as pd
|
||
import undetected_chromedriver as uc
|
||
from bs4 import BeautifulSoup
|
||
from selenium.webdriver.common.keys import Keys
|
||
import os
|
||
import json
|
||
|
||
def scrape_alternativeto(url, headless=True):
|
||
# Konfiguracja Selenium (undetected-chromedriver)
|
||
options = uc.ChromeOptions()
|
||
options.add_argument("--disable-blink-features=AutomationControlled")
|
||
|
||
# Dodaj opcję headless, jeśli ustawiona na True
|
||
if headless:
|
||
options.add_argument("--headless")
|
||
options.add_argument("--window-size=1920,1080")
|
||
options.add_argument("--start-maximized")
|
||
print("🔍 Uruchamiam przeglądarkę w trybie headless (zminimalizowanym)")
|
||
else:
|
||
print("🔍 Uruchamiam przeglądarkę w trybie widocznym")
|
||
|
||
driver = uc.Chrome(options=options)
|
||
driver.get(url)
|
||
|
||
# Poczekaj na załadowanie strony
|
||
time.sleep(5) # Zwiększam czas oczekiwania do 5 sekund
|
||
|
||
# Akceptacja cookies jeśli pojawi się banner
|
||
try:
|
||
consent_button = driver.find_element("xpath", "//button[contains(., 'Consent') or contains(., 'Zgadzam się')]")
|
||
consent_button.click()
|
||
time.sleep(2) # Zwiększam czas oczekiwania po kliknięciu
|
||
print("✅ Zaakceptowano cookies")
|
||
except Exception as e:
|
||
print(f"ℹ️ Nie znaleziono bannera cookies lub inny błąd: {str(e)}")
|
||
|
||
# Przewiń stronę, aby załadować więcej informacji
|
||
print("🔄 Przewijanie strony...")
|
||
body = driver.find_element("tag name", "body")
|
||
for i in range(15): # Zwiększam liczbę przewinięć
|
||
body.send_keys(Keys.PAGE_DOWN)
|
||
time.sleep(1)
|
||
if i % 5 == 0:
|
||
print(f" - Przewinięto {i+1} razy")
|
||
|
||
# Pobierz HTML strony
|
||
print("📄 Pobieranie pełnego HTML strony...")
|
||
html = driver.page_source
|
||
driver.quit()
|
||
print("🔍 Rozpoczynam analizę HTML...")
|
||
|
||
# Zapisz HTML do pliku dla debugowania
|
||
with open("debug_page.html", "w", encoding="utf-8") as f:
|
||
f.write(html)
|
||
print("💾 Zapisano HTML do pliku debug_page.html dla analizy")
|
||
|
||
# Parsowanie HTML za pomocą BeautifulSoup
|
||
soup = BeautifulSoup(html, "html.parser")
|
||
|
||
# Użyj podanego selektora do znalezienia informacji o CMS-ach
|
||
cms_items = soup.select("#mainContent > section > div.blue-box.PageIntroWrapper_wrapper__bHA4J.PageIntroWrapper_noClouds__yfmJz > div > header")
|
||
print(f"🔍 Znaleziono {len(cms_items)} głównych elementów CMS")
|
||
|
||
if not cms_items:
|
||
print("⚠️ Nie znaleziono głównego elementu CMS! Sprawdź poprawny selektor w przeglądarce.")
|
||
# Próba znalezienia głównych elementów strony dla diagnostyki
|
||
main_content = soup.select("#mainContent")
|
||
print(f" - Czy istnieje #mainContent? {len(main_content) > 0}")
|
||
sections = soup.select("#mainContent > section")
|
||
print(f" - Liczba sekcji w #mainContent: {len(sections)}")
|
||
return
|
||
|
||
# Lista na wyniki
|
||
extracted_data = []
|
||
|
||
# Pobranie głównych danych
|
||
for item in cms_items:
|
||
try:
|
||
name_element = item.select_one("h1")
|
||
name = name_element.text.strip() if name_element else "Brak nazwy"
|
||
print(f"📌 Nazwa CMS: {name}")
|
||
|
||
# Próba wydobycia linku (jeśli istnieje)
|
||
link_element = item.select_one("a")
|
||
link = link_element.get("href") if link_element else ""
|
||
print(f"🔗 Link: {link}")
|
||
|
||
# Przygotowanie słownika na dane
|
||
cms_data = {
|
||
"name": name,
|
||
"link": link,
|
||
"source": "alternativeto.net"
|
||
}
|
||
|
||
# Dodajemy do listy wyników
|
||
extracted_data.append(cms_data)
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas wydobywania podstawowych danych: {e}")
|
||
|
||
# Jeśli znaleziono co najmniej jeden CMS, pobierz pozostałe dane
|
||
if extracted_data:
|
||
cms_data = extracted_data[0] # W tym przypadku skupiamy się na pierwszym znalezionym CMS
|
||
|
||
# 1. Pobieranie description z podanego selektora (zaktualizowany)
|
||
print("🔍 Próba pobrania opisu...")
|
||
try:
|
||
# Nowy selektor dla opisu
|
||
description_selector = "#mainContent > section > div.blue-box.PageIntroWrapper_wrapper__bHA4J.PageIntroWrapper_noClouds__yfmJz > div > header > div.AppItem_shared_itemBox__fcqom.AppItemAbout_itemBoxAbout__egNDI > div.AppItem_shared_info__uqY4g.AppItemAbout_infoAbout__szbqZ > div.AppItem_shared_desc___QPnv.AppItemAbout_descAbout__Z7Qlv"
|
||
description_element = soup.select_one(description_selector)
|
||
if description_element:
|
||
# Pobierz tekst ze wszystkich paragrafów wewnątrz tego elementu
|
||
paragraphs = description_element.find_all("p")
|
||
if paragraphs:
|
||
cms_data["description"] = " ".join([p.text.strip() for p in paragraphs])
|
||
print(f"📝 Opis: {cms_data['description'][:50]}...")
|
||
else:
|
||
# Jeśli nie ma paragrafów, pobierz cały tekst z tego elementu
|
||
cms_data["description"] = description_element.text.strip()
|
||
print(f"📝 Opis (bezpośrednio z div): {cms_data['description'][:50]}...")
|
||
else:
|
||
cms_data["description"] = ""
|
||
print("⚠️ Nie znaleziono elementu opisu")
|
||
# Próbujmy znaleźć elementy pośrednie
|
||
desc_container = soup.select_one(".AppItem_shared_desc___QPnv")
|
||
print(f" - Czy istnieje .AppItem_shared_desc___QPnv? {desc_container is not None}")
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas pobierania opisu: {e}")
|
||
cms_data["description"] = ""
|
||
|
||
# 2. Pobieranie licence z podanego selektora
|
||
print("🔍 Próba pobrania licencji...")
|
||
try:
|
||
licence_selector = "#mainContent > section > div.blue-box.PageIntroWrapper_wrapper__bHA4J.PageIntroWrapper_noClouds__yfmJz > div > header > div.AppItemAbout_lowerAbout__gLc_7 > div.flex.flex-row.flex-wrap.gap-x-10.grow > div:nth-child(1) > ul > li"
|
||
licence_element = soup.select_one(licence_selector)
|
||
if licence_element:
|
||
cms_data["licence"] = licence_element.text.strip()
|
||
print(f"📄 Licencja: {cms_data['licence']}")
|
||
else:
|
||
cms_data["licence"] = ""
|
||
print("⚠️ Nie znaleziono elementu licencji")
|
||
# Spróbujmy prostszego selektora
|
||
licence_items = soup.select("div.AppItemAbout_lowerAbout__gLc_7 li")
|
||
print(f" - Znaleziono {len(licence_items)} elementów li w sekcji dolnej")
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas pobierania licencji: {e}")
|
||
cms_data["licence"] = ""
|
||
|
||
# 3. Pobieranie operation_system jako tablicy z podanego selektora
|
||
print("🔍 Próba pobrania systemów operacyjnych...")
|
||
try:
|
||
os_selector = "#mainContent > section > div.blue-box.PageIntroWrapper_wrapper__bHA4J.PageIntroWrapper_noClouds__yfmJz > div > header > div.AppItemAbout_lowerAbout__gLc_7 > div.flex.flex-row.flex-wrap.gap-x-10.grow > div.grow > ul > li"
|
||
os_elements = soup.select(os_selector)
|
||
if os_elements:
|
||
cms_data["operation_system"] = [os_item.text.strip() for os_item in os_elements]
|
||
print(f"💻 Systemy operacyjne: {cms_data['operation_system']}")
|
||
else:
|
||
cms_data["operation_system"] = []
|
||
print("⚠️ Nie znaleziono elementów systemów operacyjnych")
|
||
# Spróbujmy prostszego selektora
|
||
os_items = soup.select("div.grow > ul > li")
|
||
print(f" - Znaleziono {len(os_items)} elementów li w sekcji systemów")
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas pobierania systemów operacyjnych: {e}")
|
||
cms_data["operation_system"] = []
|
||
|
||
# 4. Pobieranie properties jako tablicy
|
||
print("🔍 Próba pobrania właściwości...")
|
||
try:
|
||
property_selector = "#mainContent > section > div:nth-child(5) > div.Box_box__20D8q.Box_transparentBox__Dj_jC.commonBoxList > div.HighlightFeatures_featureContainer__85Z5p > div > ol:nth-child(2) > li"
|
||
property_elements = soup.select(property_selector)
|
||
if property_elements:
|
||
cms_data["properties"] = [prop.text.strip() for prop in property_elements]
|
||
print(f"🔧 Właściwości: znaleziono {len(cms_data['properties'])} elementów")
|
||
if cms_data["properties"]:
|
||
print(f" Przykład: {cms_data['properties'][0]}")
|
||
else:
|
||
cms_data["properties"] = []
|
||
print("⚠️ Nie znaleziono elementów właściwości")
|
||
# Spróbujmy prostszego selektora
|
||
all_ol = soup.select("div.HighlightFeatures_featureContainer__85Z5p ol")
|
||
print(f" - Znaleziono {len(all_ol)} list ol w kontenerze funkcji")
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas pobierania właściwości: {e}")
|
||
cms_data["properties"] = []
|
||
|
||
# 5. Pobieranie features jako tablicy
|
||
print("🔍 Próba pobrania funkcji...")
|
||
try:
|
||
feature_selector = "#mainContent > section > div:nth-child(5) > div.Box_box__20D8q.Box_transparentBox__Dj_jC.commonBoxList > div.HighlightFeatures_featureContainer__85Z5p > div > ol:nth-child(6) > li"
|
||
feature_elements = soup.select(feature_selector)
|
||
if feature_elements:
|
||
cms_data["features"] = [feature.text.strip() for feature in feature_elements]
|
||
print(f"✨ Funkcje: znaleziono {len(cms_data['features'])} elementów")
|
||
if cms_data["features"]:
|
||
print(f" Przykład: {cms_data['features'][0]}")
|
||
else:
|
||
cms_data["features"] = []
|
||
print("⚠️ Nie znaleziono elementów funkcji")
|
||
# Spróbujmy innego selektora dla list funkcji
|
||
all_lists = soup.select("div.HighlightFeatures_featureContainer__85Z5p ol li")
|
||
print(f" - Znaleziono {len(all_lists)} elementów li w listach funkcji")
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas pobierania funkcji: {e}")
|
||
cms_data["features"] = []
|
||
|
||
# 6. Pobieranie url z podanego selektora (zaktualizowany)
|
||
print("🔍 Próba pobrania URL...")
|
||
try:
|
||
# Zaktualizowany selektor dla URL
|
||
url_selector = "#mainContent > section > div:nth-child(6) > div.styles_mainGrid__5RbQK > div.styles_mainGridRight__gHR6o > div > div:nth-child(3) > div.AppExternalLinks_linkContainer__1kDP1 > a:nth-child(1) > button > span"
|
||
url_span_element = soup.select_one(url_selector)
|
||
|
||
if url_span_element:
|
||
# Pobierz tekst ze spana
|
||
url_text = url_span_element.text.strip()
|
||
cms_data["url"] = url_text
|
||
print(f"🌐 URL (tekst z przycisku): {url_text}")
|
||
else:
|
||
# Próba alternatywnego podejścia - znalezienie linku (tagu a)
|
||
url_link_selector = "#mainContent > section > div:nth-child(6) > div.styles_mainGrid__5RbQK > div.styles_mainGridRight__gHR6o > div > div:nth-child(3) > div.AppExternalLinks_linkContainer__1kDP1 > a:nth-child(1)"
|
||
url_link_element = soup.select_one(url_link_selector)
|
||
|
||
if url_link_element:
|
||
href = url_link_element.get("href", "")
|
||
# Sprawdź, czy href zawiera rzeczywisty URL (pełny), a nie tylko ścieżkę względną
|
||
if href.startswith("http"):
|
||
cms_data["url"] = href
|
||
else:
|
||
# Jeśli to ścieżka względna, spróbuj pobrać tekst z przycisku wewnątrz linku
|
||
button_element = url_link_element.select_one("button")
|
||
if button_element:
|
||
cms_data["url"] = button_element.text.strip()
|
||
else:
|
||
cms_data["url"] = href # Użyj ścieżki względnej jako ostateczność
|
||
print(f"🌐 URL (z tagu a): {cms_data['url']}")
|
||
else:
|
||
cms_data["url"] = ""
|
||
print("⚠️ Nie znaleziono elementu URL")
|
||
# Spróbujmy prostszego selektora
|
||
link_containers = soup.select("div.AppExternalLinks_linkContainer__1kDP1 a")
|
||
print(f" - Znaleziono {len(link_containers)} linków w kontenerze linków zewnętrznych")
|
||
if link_containers:
|
||
for i, link in enumerate(link_containers[:3]): # Pokaż tylko pierwsze 3
|
||
print(f" Link {i+1}: href={link.get('href', 'brak')}, tekst={link.text.strip()[:30]}...")
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas pobierania URL: {e}")
|
||
cms_data["url"] = ""
|
||
|
||
# Aktualizuj listę wyników
|
||
extracted_data[0] = cms_data
|
||
|
||
if not extracted_data:
|
||
print("⚠️ Nie udało się wydobyć żadnych danych.")
|
||
return
|
||
|
||
# Dopisywanie do istniejących danych JSON (jeśli plik istnieje)
|
||
json_path = "cms_all.json"
|
||
if os.path.exists(json_path):
|
||
with open(json_path, "r", encoding="utf-8") as f:
|
||
try:
|
||
existing_data = json.load(f)
|
||
print(f"📊 Wczytano {len(existing_data)} istniejących rekordów z JSON")
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas odczytu pliku JSON: {e}")
|
||
existing_data = []
|
||
else:
|
||
existing_data = []
|
||
print("ℹ️ Plik JSON nie istnieje, tworzę nowy")
|
||
|
||
# Dodaj nowe dane, unikając duplikatów po nazwie i linku
|
||
existing_keys = {(d["name"], d["link"]) for d in existing_data if "name" in d and "link" in d}
|
||
added = 0
|
||
for row in extracted_data:
|
||
if (row["name"], row["link"]) not in existing_keys:
|
||
existing_data.append(row)
|
||
added += 1
|
||
print(f"📊 Dodano {added} nowych rekordów do JSON")
|
||
|
||
# Zapisz zaktualizowane dane
|
||
with open(json_path, "w", encoding="utf-8") as f:
|
||
json.dump(existing_data, f, indent=4, ensure_ascii=False)
|
||
print(f"✅ Dane dopisane do {json_path}")
|
||
|
||
# Analogicznie dla CSV
|
||
import pandas as pd
|
||
df_new = pd.DataFrame(extracted_data)
|
||
csv_path = "cms_all.csv"
|
||
if os.path.exists(csv_path):
|
||
try:
|
||
df_existing = pd.read_csv(csv_path, encoding="utf-8")
|
||
print(f"📊 Wczytano {len(df_existing)} istniejących rekordów z CSV")
|
||
df_all = pd.concat([df_existing, df_new], ignore_index=True)
|
||
df_all = df_all.drop_duplicates(subset=["name", "link"])
|
||
except Exception as e:
|
||
print(f"⚠️ Błąd podczas odczytu pliku CSV: {e}")
|
||
df_all = df_new
|
||
else:
|
||
df_all = df_new
|
||
print("ℹ️ Plik CSV nie istnieje, tworzę nowy")
|
||
|
||
df_all.to_csv(csv_path, index=False, encoding="utf-8")
|
||
print(f"✅ Dane dopisane do {csv_path}")
|
||
print(f"📊 Łącznie zapisano {len(df_all)} rekordów do CSV")
|
||
|
||
def show_descriptions_from_csv():
|
||
"""
|
||
Funkcja otwierająca plik cms_alternatives.csv, pobierająca atrybut link
|
||
i wyświetlająca jego wartość dla każdego rekordu.
|
||
"""
|
||
print("\n🔗 Odczytywanie linków z pliku cms_alternatives.csv...")
|
||
|
||
try:
|
||
# Sprawdź, czy plik istnieje
|
||
alternative_csv_path = "cms_alternatives.csv"
|
||
if not os.path.exists(alternative_csv_path):
|
||
print(f"⚠️ Plik {alternative_csv_path} nie istnieje!")
|
||
return
|
||
|
||
# Wczytaj plik CSV
|
||
df = pd.read_csv(alternative_csv_path, encoding="utf-8")
|
||
|
||
# Sprawdź, czy kolumna link istnieje
|
||
if "link" not in df.columns:
|
||
print("⚠️ Kolumna 'link' nie istnieje w pliku CSV!")
|
||
print(f"Dostępne kolumny: {', '.join(df.columns)}")
|
||
return
|
||
|
||
# Wyświetl informacje o pliku
|
||
print(f"📊 Znaleziono {len(df)} rekordów w pliku {alternative_csv_path}")
|
||
|
||
# Wyświetl linki dla każdego rekordu
|
||
print("\n=== LINKI CMS-ÓW ===")
|
||
for i, row in df.iterrows():
|
||
name = row.get("name", f"Rekord {i+1}")
|
||
link = row.get("link", "Brak linku")
|
||
|
||
print(f"\n📌 {name}:")
|
||
print(f"🔗 {link}")
|
||
scrape_alternativeto(link, headless=True)
|
||
|
||
|
||
print("\n=== KONIEC LINKÓW ===")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ Wystąpił błąd podczas odczytywania pliku: {str(e)}")
|
||
|
||
# Uruchamiamy skrypt
|
||
if __name__ == "__main__":
|
||
print("🚀 Rozpoczynam scraping AlternativeTo...")
|
||
## scrape_alternativeto('https://alternativeto.net/software/boomla/about/')
|
||
print("✅ Scraping zakończony")
|
||
|
||
# Uruchom funkcję pokazującą linki z pliku CSV
|
||
show_descriptions_from_csv()
|