Files
polit-scraper/web_check.py

164 lines
5.8 KiB
Python

import cloudscraper
import json
import os
import subprocess
import difflib
import sys
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from datetime import datetime
from dotenv import load_dotenv
# --- KONFIGURATION ---
# Laden der Umgebungsvariablen aus .env Datei
load_dotenv()
ADMIN_USER = os.getenv("ADMIN_USER")
ADMIN_PW = os.getenv("ADMIN_PW")
NTFY_BASE = os.getenv("NTFY_BASE")
JSON_DATEI = "./results.json"
GEMINI_SESSION = "1"
# ntfy-Themen (Öffentlich)
TOPIC_PUBLIC = "polit-scraper-public"
TOPIC_ADMIN = "polit-scraper-admin"
# UTF-8 für Konsole erzwingen
sys.stdout.reconfigure(encoding='utf-8')
# Cloudscraper initialisieren (Chrome-Emulation)
scraper = cloudscraper.create_scraper(
browser={'browser': 'chrome', 'platform': 'windows', 'desktop': True}
)
def analysiere_mit_gemini(text, links):
"""KI-Analyse: Text und Links an Gemini senden, JSON extrahieren."""
links_text = "\nRelevante Links: " + ", ".join(links) if links else ""
anweisung = f"Analysiere diesen Text: {text}{links_text}"
befehl = ["gemini", "-r", GEMINI_SESSION, "-p", anweisung]
try:
prozess = subprocess.run(befehl, capture_output=True, text=True, encoding='utf-8')
antwort_roh = prozess.stdout.strip()
start = antwort_roh.find('{')
ende = antwort_roh.rfind('}') + 1
if start != -1 and ende != -1:
return json.loads(antwort_roh[start:ende])
return {"relevant": False}
except Exception as e:
print(f" [!] KI-Fehler: {e}")
return {"relevant": False}
def sende_public(titel, nachricht, klick_url, category="info", priority=3):
"""Öffentlicher ntfy-Versand mit Priorität und Klick-URL."""
if not NTFY_BASE: return
url = f"{NTFY_BASE}/{TOPIC_PUBLIC}"
emoji_mapping = {
"demo": "rotating_light,loudspeaker",
"socialmedia": "detective,no_entry",
"polizei": "police_car,warning",
"stadt": "cityscape,newspaper",
"solidaritaet": "fist,heart",
"info": "newspaper"
}
tags = emoji_mapping.get(category.lower(), "newspaper")
headers = {
"Title": titel.encode('utf-8'),
"Tags": tags,
"Click": klick_url,
"Priority": str(priority)
}
try:
import requests
requests.post(url, data=nachricht.encode("utf-8"), headers=headers,
auth=(ADMIN_USER, ADMIN_PW), timeout=5)
except Exception as e:
print(f" [!] Fehler Public-Versand: {e}")
def sende_admin(titel, nachricht):
"""Admin-Benachrichtigung bei technischen Fehlern."""
if not NTFY_BASE: return
url = f"{NTFY_BASE}/{TOPIC_ADMIN}"
headers = {"Title": titel.encode('utf-8'), "Tags": "warning,skull", "Priority": "4"}
try:
import requests
requests.post(url, data=nachricht.encode("utf-8"), headers=headers,
auth=(ADMIN_USER, ADMIN_PW), timeout=5)
except Exception as e:
print(f" [!] Fehler Admin-Versand: {e}")
def lade_gedaechtnis():
if not os.path.exists(JSON_DATEI): return {}
with open(JSON_DATEI, "r", encoding='utf-8') as f:
try: return json.load(f)
except: return {}
def speichere_gedaechtnis(daten):
with open(JSON_DATEI, "w", encoding='utf-8') as f:
json.dump(daten, f, indent=4, ensure_ascii=False)
def extrahiere_links(soup, basis_url):
links = []
for a in soup.find_all('a', href=True):
links.append(urljoin(basis_url, a['href']))
return list(set(links))
def verarbeite_website(url, gedaechtnis):
try:
antwort = scraper.get(url, timeout=15)
antwort.encoding = 'utf-8'
status = antwort.status_code
if status != 200:
print(f" [!] Seite fehlerhaft: {url} (Status: {status})")
sende_admin("Fehler bei Webseite", f"{url} lieferte Status {status}.")
return
soup = BeautifulSoup(antwort.text, "html.parser")
hauptbereich = soup.find("main") or soup.find("article") or soup.body or soup
neuer_text = hauptbereich.get_text(separator=" ", strip=True)
alter_eintrag = gedaechtnis.get(url, {})
alter_text = alter_eintrag.get("inhalt", "")
if alter_text != neuer_text:
print(f" [NEU] Änderung auf {url} gefunden. Ich frage die KI...")
alt_z = alter_text.splitlines()
neu_z = neuer_text.splitlines()
diff = [z[2:] for z in difflib.ndiff(alt_z, neu_z) if z.startswith("+ ")]
text_fuer_ki = "\n".join(diff)
alle_links = extrahiere_links(hauptbereich, url)
if text_fuer_ki.strip():
analyse = analysiere_mit_gemini(text_fuer_ki, alle_links)
if analyse.get("relevant") == True:
print(f" [!] KI bestätigt Relevanz: {analyse.get('titel')}")
klick_url = analyse.get("direct_link", url)
sende_public(
titel=analyse.get("titel", "Polit-Update"),
nachricht=analyse.get("nachricht", "Neu auf der Seite"),
klick_url=klick_url,
category=analyse.get("category", "info"),
priority=analyse.get("priority", 3)
)
gedaechtnis[url] = {"inhalt": neuer_text, "zeit": str(datetime.now()), "status": status}
else:
print(f" [OK] Keine Änderungen auf {url}.")
except Exception as e:
print(f" ❌ Fehler bei {url}: {e}")
sende_admin("Programmfehler", f"{url}: {e}")
# --- HAUPTPROGRAMM ---
gedaechtnis = lade_gedaechtnis()
with open("./urls.txt", "r", encoding='utf-8') as datei:
for zeile in datei:
adresse = zeile.strip()
if not adresse or adresse.startswith("#"): continue
print(f"Ich prüfe: {adresse}")
verarbeite_website(adresse, gedaechtnis)
speichere_gedaechtnis(gedaechtnis)
print("\nFertig. Gute Nacht!")