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
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Version: v1.6.0 (Refactored)
|
Version: v1.6.1 (Refactored)
|
||||||
Datum: {aktuelles Datum}
|
Datum: {aktuelles Datum}
|
||||||
Git-Überschrift (max. 100 Zeichen):
|
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:
|
Git-Änderungsbeschreibung:
|
||||||
- Doppelte Funktionen (process_verification_only, _process_batch, count_linkedin_contacts) entfernt.
|
- Überarbeite `get_website_raw` zur besseren Handhabung von Cookie-Bannern.
|
||||||
- Doppelte DataProcessor-Klasse entfernt.
|
- Priorisiere Scraping von Hauptinhalt-Tags (`<main>`, `<article>`, spezifische IDs/Klassen).
|
||||||
- Ungenutzten Code (Timestamp-Handling, compare_umsatz_values, process_contacts) entfernt.
|
- Implementiere Fallback auf `<body>` mit Versuch, häufige Banner-Elemente zu entfernen (`.decompose()`).
|
||||||
- Google Sheet Updates optimiert durch konsequentere Nutzung von batch_update in Schleifen (_process_batch, process_website_batch, process_branch_batch, process_contact_research).
|
- Füge Heuristik hinzu, um extrahierten Text zu verwerfen, wenn er wahrscheinlich nur Banner-Inhalt ist.
|
||||||
- API-Key-Handling zentralisiert: Keys werden einmal in Config geladen und von dort bezogen.
|
- Erhöhe Request-Timeout in `get_website_raw` leicht auf 15 Sekunden.
|
||||||
- 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.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -51,7 +46,7 @@ LOG_DIR = "Log"
|
|||||||
|
|
||||||
# ==================== KONFIGURATION ====================
|
# ==================== KONFIGURATION ====================
|
||||||
class Config:
|
class Config:
|
||||||
VERSION = "v1.6.0"
|
VERSION = "v1.6.1"
|
||||||
LANG = "de"
|
LANG = "de"
|
||||||
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
|
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
|
||||||
MAX_RETRIES = 3
|
MAX_RETRIES = 3
|
||||||
@@ -1113,53 +1108,98 @@ class WikipediaScraper:
|
|||||||
|
|
||||||
@retry_on_failure
|
@retry_on_failure
|
||||||
def get_website_raw(url, max_length=1000, verify_cert=False):
|
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.':
|
if not url or not isinstance(url, str) or url.strip().lower() == 'k.a.':
|
||||||
return "k.A."
|
return "k.A."
|
||||||
|
|
||||||
# Normalisiere URL und füge https hinzu wenn nötig
|
|
||||||
if not url.lower().startswith("http"):
|
if not url.lower().startswith("http"):
|
||||||
url = "https://" + url
|
url = "https://" + url
|
||||||
|
|
||||||
headers = {
|
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"
|
"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:
|
try:
|
||||||
# Deaktiviere Zertifikatsprüfung explizit, falls verify_cert=False
|
response = requests.get(url, timeout=15, headers=headers, verify=verify_cert) # Etwas längerer Timeout
|
||||||
response = requests.get(url, timeout=10, headers=headers, verify=verify_cert)
|
response.raise_for_status()
|
||||||
response.raise_for_status() # Fehler bei Statuscodes >= 400
|
|
||||||
|
|
||||||
# Encoding prüfen und ggf. korrigieren
|
|
||||||
response.encoding = response.apparent_encoding
|
response.encoding = response.apparent_encoding
|
||||||
|
|
||||||
soup = BeautifulSoup(response.text, Config.HTML_PARSER)
|
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
|
# --- Versuch 1: Hauptinhalt-Tags finden ---
|
||||||
body = soup.find('body')
|
content_area = (
|
||||||
content_area = soup.find('main') or body # Bevorzuge <main>, sonst <body>
|
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:
|
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 = content_area.get_text(separator=' ', strip=True)
|
||||||
text = re.sub(r'\s+', ' ', text) # Normalisiere Whitespace
|
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]
|
result = text[:max_length]
|
||||||
debug_print(f"Website {url} erfolgreich gescrapt. Extrahierter Text (Länge {len(result)}): {result[:100]}...")
|
debug_print(f"Website {url} erfolgreich gescrapt. Extrahierter Text (Länge {len(result)}): {result[:100]}...")
|
||||||
return result
|
return result
|
||||||
else:
|
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."
|
return "k.A."
|
||||||
except requests.exceptions.SSLError as e:
|
except requests.exceptions.SSLError as e:
|
||||||
debug_print(f"SSL-Fehler beim Abrufen der Website {url}: {e}. Versuche ohne Zertifikatsprüfung...")
|
debug_print(f"SSL-Fehler beim Abrufen der Website {url}: {e}. Versuche ohne Zertifikatsprüfung...")
|
||||||
# Erneuter Versuch ohne Verifizierung
|
if verify_cert:
|
||||||
if verify_cert: # Nur wenn der erste Versuch mit Verifizierung war
|
return get_website_raw(url, max_length, verify_cert=False)
|
||||||
return get_website_raw(url, max_length, verify_cert=False)
|
else:
|
||||||
else:
|
return "k.A."
|
||||||
return "k.A." # Wenn auch ohne Verifizierung Fehler auftritt
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
debug_print(f"Netzwerk-/HTTP-Fehler beim Abrufen der Website {url}: {e}")
|
debug_print(f"Netzwerk-/HTTP-Fehler beim Abrufen der Website {url}: {e}")
|
||||||
return "k.A."
|
return "k.A."
|
||||||
|
|||||||
Reference in New Issue
Block a user