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:
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user