v1.6.1: Verbessere Website-Scraping zur Umgehung von Cookie-Bannern

- Überarbeite `get_website_raw` zur besseren Handhabung von Cookie-Bannern.
- Priorisiere Scraping von Hauptinhalt-Tags (`<main>`, `<article>`, spezifische IDs/Klassen).
- Implementiere Fallback auf `<body>` mit Versuch, häufige Banner-Elemente zu entfernen (`.decompose()`).
- Füge Heuristik hinzu, um extrahierten Text zu verwerfen, wenn er wahrscheinlich nur Banner-Inhalt ist.
- Erhöhe Request-Timeout in `get_website_raw` leicht auf 15 Sekunden.
This commit is contained in:
2025-04-15 20:14:47 +00:00
parent b171d4b82e
commit 27604a8a79

View File

@@ -1,21 +1,16 @@
#!/usr/bin/env python3
"""
Version: v1.6.0 (Refactored)
Version: v1.6.1 (Refactored)
Datum: {aktuelles Datum}
Git-Überschrift (max. 100 Zeichen):
v1.6.0: Refactoring - Code-Optimierung und Beseitigung von Redundanzen
v1.6.1: Verbessere Website-Scraping zur Umgehung von Cookie-Bannern
Git-Änderungsbeschreibung:
- Doppelte Funktionen (process_verification_only, _process_batch, count_linkedin_contacts) entfernt.
- Doppelte DataProcessor-Klasse entfernt.
- Ungenutzten Code (Timestamp-Handling, compare_umsatz_values, process_contacts) entfernt.
- Google Sheet Updates optimiert durch konsequentere Nutzung von batch_update in Schleifen (_process_batch, process_website_batch, process_branch_batch, process_contact_research).
- API-Key-Handling zentralisiert: Keys werden einmal in Config geladen und von dort bezogen.
- Google Sheet Verbindung zentralisiert: Wird nur noch im GoogleSheetHandler aufgebaut.
- Vorbereitung für robustere Spaltenzugriffe durch Einführung einer COLUMN_MAP (noch nicht durchgängig genutzt).
- Modus-Steuerung in main() konsolidiert.
- alignment_demo korrigiert (nur noch für Hauptblatt). Header für Contacts-Blatt in process_contact_research gesetzt.
- Konstanten für Dateinamen eingeführt.
- Überarbeite `get_website_raw` zur besseren Handhabung von Cookie-Bannern.
- Priorisiere Scraping von Hauptinhalt-Tags (`<main>`, `<article>`, spezifische IDs/Klassen).
- Implementiere Fallback auf `<body>` mit Versuch, häufige Banner-Elemente zu entfernen (`.decompose()`).
- Füge Heuristik hinzu, um extrahierten Text zu verwerfen, wenn er wahrscheinlich nur Banner-Inhalt ist.
- Erhöhe Request-Timeout in `get_website_raw` leicht auf 15 Sekunden.
"""
import os
@@ -51,7 +46,7 @@ LOG_DIR = "Log"
# ==================== KONFIGURATION ====================
class Config:
VERSION = "v1.6.0"
VERSION = "v1.6.1"
LANG = "de"
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
MAX_RETRIES = 3
@@ -1113,53 +1108,98 @@ class WikipediaScraper:
@retry_on_failure
def get_website_raw(url, max_length=1000, verify_cert=False):
"""Holt Textinhalt von einer Website."""
"""Holt Textinhalt von einer Website, versucht Cookie-Banner zu umgehen."""
if not url or not isinstance(url, str) or url.strip().lower() == 'k.a.':
return "k.A."
# Normalisiere URL und füge https hinzu wenn nötig
if not url.lower().startswith("http"):
url = "https://" + url
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
}
try:
# Deaktiviere Zertifikatsprüfung explizit, falls verify_cert=False
response = requests.get(url, timeout=10, headers=headers, verify=verify_cert)
response.raise_for_status() # Fehler bei Statuscodes >= 400
# Encoding prüfen und ggf. korrigieren
response = requests.get(url, timeout=15, headers=headers, verify=verify_cert) # Etwas längerer Timeout
response.raise_for_status()
response.encoding = response.apparent_encoding
soup = BeautifulSoup(response.text, Config.HTML_PARSER)
# Entferne Skript- und Style-Tags
for script_or_style in soup(["script", "style"]):
script_or_style.decompose()
# Finde body oder main content
body = soup.find('body')
content_area = soup.find('main') or body # Bevorzuge <main>, sonst <body>
# --- Versuch 1: Hauptinhalt-Tags finden ---
content_area = (
soup.find('main') or # Suche <main>
soup.find('article') or # Oder <article>
soup.find(id='content') or # Oder <div id="content">
soup.find(id='main-content') or # Oder <div id="main-content">
soup.find(class_='main-content') or # Oder <div class="main-content">
soup.find(class_='content') # Oder <div class="content">
# Füge ggf. weitere spezifische Selektoren hinzu, die du oft siehst
)
if content_area:
# Extrahiere Text, trenne mit Leerzeichen, entferne überflüssigen Whitespace
debug_print(f"Gezielten Inhaltsbereich gefunden ({content_area.name}#{content_area.get('id')} oder .{content_area.get('class')}) für {url}")
else:
# --- Fallback: Body nehmen, ABER Banner versuchen zu entfernen ---
debug_print(f"Kein spezifischer Inhaltsbereich gefunden für {url}. Nutze Body und versuche Banner zu entfernen.")
content_area = soup.find('body')
if content_area:
# Versuche, häufige Cookie-Banner Strukturen zu entfernen
banner_selectors = [
'[id*="cookie"]', # IDs die "cookie" enthalten
'[class*="cookie"]', # Klassen die "cookie" enthalten
'[id*="consent"]', # IDs die "consent" enthalten
'[class*="consent"]', # Klassen die "consent" enthalten
'[id*="banner"]', # IDs die "banner" enthalten (vorsichtig!)
'[class*="banner"]', # Klassen die "banner" enthalten (vorsichtig!)
'[role="dialog"]' # Oft für Popups/Banner genutzt (vorsichtig!)
]
banners_removed_count = 0
for selector in banner_selectors:
try:
potential_banners = content_area.select(selector)
for banner in potential_banners:
# Zusätzliche Prüfung: Enthält das Element typischen Banner-Text?
banner_text = banner.get_text(" ", strip=True).lower()
keywords = ["cookie", "zustimm", "ablehnen", "einverstanden", "datenschutz", "privacy", "akzeptier"]
if any(keyword in banner_text for keyword in keywords):
debug_print(f"Entferne potenzielles Banner ({selector}) mit Text: {banner_text[:100]}...")
banner.decompose() # Entferne das Element aus dem Baum
banners_removed_count += 1
except Exception as e_select:
debug_print(f"Fehler beim Versuch Banner mit Selektor '{selector}' zu entfernen: {e_select}")
if banners_removed_count > 0:
debug_print(f"{banners_removed_count} potenzielle Banner-Elemente entfernt.")
# --- Text extrahieren aus gefundenem Bereich (oder Body) ---
if content_area:
for script_or_style in content_area(["script", "style"]): # Skripte/Styles entfernen
script_or_style.decompose()
text = content_area.get_text(separator=' ', strip=True)
text = re.sub(r'\s+', ' ', text) # Normalisiere Whitespace
# --- Zusätzliche Prüfung: Ist der extrahierte Text *nur* Banner-Text? ---
banner_keywords_strict = ["cookie", "zustimmen", "ablehnen", "einverstanden", "datenschutz", "privacy", "akzeptier", "einstellung", "partner", "analyse", "marketing"]
text_lower = text.lower()
keyword_hits = sum(1 for keyword in banner_keywords_strict if keyword in text_lower)
# Heuristik: Wenn der Text kurz ist UND viele Banner-Keywords enthält -> Verwerfen
if len(text) < 500 and keyword_hits >= 3:
debug_print(f"WARNUNG: Extrahierter Text für {url} scheint nur Cookie-Banner zu sein (Länge {len(text)}, {keyword_hits} Keywords). Verwerfe Text.")
return "k.A. (Nur Cookie-Banner erkannt)"
result = text[:max_length]
debug_print(f"Website {url} erfolgreich gescrapt. Extrahierter Text (Länge {len(result)}): {result[:100]}...")
return result
else:
debug_print(f"Kein <body> oder <main> Tag gefunden in {url}")
debug_print(f"Kein <body> oder spezifischer Inhaltsbereich gefunden in {url}")
return "k.A."
except requests.exceptions.SSLError as e:
debug_print(f"SSL-Fehler beim Abrufen der Website {url}: {e}. Versuche ohne Zertifikatsprüfung...")
# Erneuter Versuch ohne Verifizierung
if verify_cert: # Nur wenn der erste Versuch mit Verifizierung war
return get_website_raw(url, max_length, verify_cert=False)
else:
return "k.A." # Wenn auch ohne Verifizierung Fehler auftritt
debug_print(f"SSL-Fehler beim Abrufen der Website {url}: {e}. Versuche ohne Zertifikatsprüfung...")
if verify_cert:
return get_website_raw(url, max_length, verify_cert=False)
else:
return "k.A."
except requests.exceptions.RequestException as e:
debug_print(f"Netzwerk-/HTTP-Fehler beim Abrufen der Website {url}: {e}")
return "k.A."