Skip to content

Instantly share code, notes, and snippets.

@dewomser
Created December 11, 2025 16:39
Show Gist options
  • Select an option

  • Save dewomser/d725743b21f7a962be244220af0375f2 to your computer and use it in GitHub Desktop.

Select an option

Save dewomser/d725743b21f7a962be244220af0375f2 to your computer and use it in GitHub Desktop.
SPD Politiker Worms. / Pythonsskript scrapet die Namen und Funktionen der Personen von der Webseite. /KI hat geholfen.
import requests
from bs4 import BeautifulSoup
import json
import re
import subprocess
def fetch_html_with_curl(url):
"""
Ruft den HTML-Inhalt von einer gegebenen URL mit curl ab, um Blockaden zu umgehen.
"""
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
result = subprocess.run(
['curl', '-A', headers['User-Agent'], '-L', url],
capture_output=True,
text=True,
check=True
)
return result.stdout
except (subprocess.CalledProcessError, FileNotFoundError) as e:
print(f"Fehler beim Abrufen von {url} mit curl: {e}")
return None
def parse_spd_fraktion(html_content):
"""
Parst die HTML-Inhalte der SPD Fraktions-Seite.
"""
if not html_content:
return []
soup = BeautifulSoup(html_content, 'html.parser')
members = []
# Jede Person ist in einer 'c-card' enthalten
cards = soup.find_all('div', class_='c-card--horizontal')
for card in cards:
name_tag = card.select_one('h1.c-card__headline span[itemprop="name"]')
if not name_tag:
continue
name = name_tag.get_text(strip=True)
# Sammle alle Funktionen
functions = []
# Die Hauptfunktion ist im "preheadline"
preheadline_tag = card.select_one('.c-card__preheadline')
if preheadline_tag and preheadline_tag.get_text(strip=True):
functions.append(preheadline_tag.get_text(strip=True))
# Weitere Funktionen sind in 'c-card__copy' als durch <br> getrennte Liste
copy_tag = card.select_one('p.c-card__copy')
if copy_tag:
# Ersetze <br> mit einem eindeutigen Trennzeichen, um den Text dann zu splitten
for br in copy_tag.find_all('br'):
br.replace_with('|||')
# Bereinige und teile den Text
function_list = copy_tag.get_text().split('|||')
for func in function_list:
# Bereinige jede Funktion und füge sie hinzu, wenn sie nicht leer ist
cleaned_func = func.replace('-', '').strip()
if cleaned_func:
functions.append(cleaned_func)
# Wenn nach all dem keine Funktion gefunden wurde, ist es ein einfaches Mitglied
if not functions:
functions.append("Fraktionsmitglied")
members.append({"name": name, "functions": functions})
return members
def parse_spd_vorstand(html_content):
"""
Parst die HTML-Inhalte der SPD Unterbezirksvorstand-Seite.
"""
if not html_content:
return []
soup = BeautifulSoup(html_content, 'html.parser')
members = []
# Die Struktur ist hier identisch zur Fraktionsseite
cards = soup.find_all('div', class_='c-card--horizontal')
for card in cards:
name_tag = card.select_one('h1.c-card__headline span[itemprop="name"]')
if not name_tag:
continue
name = name_tag.get_text(strip=True)
functions = []
preheadline_tag = card.select_one('.c-card__preheadline')
if preheadline_tag and preheadline_tag.get_text(strip=True):
functions.append(preheadline_tag.get_text(strip=True))
else:
# Fallback, falls keine Funktion angegeben ist
functions.append("Vorstandsmitglied")
members.append({"name": name, "functions": functions})
return members
def merge_politicians_data(list1, list2):
"""
Führt Daten aus beiden Listen zusammen und kombiniert Funktionen für Personen,
die in beiden Listen vorkommen.
"""
merged_data = {}
for p in list1:
name = p["name"]
merged_data[name] = {"name": name, "functions": list(set(p["functions"]))}
for p in list2:
name = p["name"]
if name in merged_data:
existing_functions = set(merged_data[name]["functions"])
new_functions = set(p["functions"])
merged_data[name]["functions"] = sorted(list(existing_functions.union(new_functions)))
else:
merged_data[name] = {"name": name, "functions": list(set(p["functions"]))}
return sorted(list(merged_data.values()), key=lambda x: x["name"])
def main():
"""
Hauptfunktion des Skripts.
"""
fraktion_url = "https://www.spd-worms.de/gruppen/fraktion/"
vorstand_url = "https://www.spd-worms.de/gruppen/unterbezirksvorstand/"
output_filename = "spd_politiker_worms.json"
print(f"Rufe HTML von {fraktion_url} ab...")
fraktion_html = fetch_html_with_curl(fraktion_url)
print(f"Rufe HTML von {vorstand_url} ab...")
vorstand_html = fetch_html_with_curl(vorstand_url)
if not fraktion_html or not vorstand_html:
print("Konnte eine oder beide Webseiten nicht abrufen. Das Skript wird beendet.")
return
print("Parse Fraktions-Daten...")
fraktion_data = parse_spd_fraktion(fraktion_html)
print("Parse Vorstands-Daten...")
vorstand_data = parse_spd_vorstand(vorstand_html)
print("Führe Daten zusammen...")
final_data = merge_politicians_data(fraktion_data, vorstand_data)
try:
with open(output_filename, 'w', encoding='utf-8') as f:
json.dump(final_data, f, ensure_ascii=False, indent=2)
print(f"Erfolgreich {output_filename} erstellt.")
except IOError as e:
print(f"Fehler beim Schreiben der Datei {output_filename}: {e}")
if __name__ == "__main__":
main()
[
{
"name": "Alexandra Zäuner",
"functions": [
"Mitglied Bau und Mobilitätsausschuss",
"Mitglied Friedhofsausschuss",
"Mitglied Jugendhilfeausschuss"
]
},
{
"name": "Alexandros Stefikos",
"functions": [
"Beisitzer",
"Fraktionsmitglied"
]
},
{
"name": "Andreas Sackreuther",
"functions": [
"Beisitzer"
]
},
{
"name": "Annika Knaub",
"functions": [
"Kassiererin"
]
},
{
"name": "Carlo Riva",
"functions": [
"Mitglied Innenstadtausschuss",
"Mitglied Jugendhilfeausschuss",
"Mitglied Beirat für Migration und Integration",
"Mitglied Haupt und Finanzausschuss",
"Mitglied im Sozialausschuss",
"Mitglied Bildungs und Schulträgerausschuss"
]
},
{
"name": "Carmen Stephan",
"functions": [
"stellvertretende Vorsitzende"
]
},
{
"name": "Daniel Usner",
"functions": [
"Schriftführer"
]
},
{
"name": "Dirk Beyer, MdL",
"functions": [
"Mitglied Rechnungsprüfungsausschuss",
"Mitglied Ausschuss für kommunale Sicherheit und Bevölkerungsschutz",
"Fraktionsvorsitzender",
"Mitglied Haupt und Finanzausschuss",
"Mitglied Bau und Mobilitätsauschuss",
"Mitglied Regionaltag Rheinhessen"
]
},
{
"name": "Dr. David Maier",
"functions": [
"stellvertretender Vorsitzender"
]
},
{
"name": "Edith Brodhäcker",
"functions": [
"stellvertretende Schriftführerin"
]
},
{
"name": "Heidi Lammeyer",
"functions": [
"Mitglied Umlegungsausschuss",
"Mitglied Kulturausschuss",
"Mitglied Bau und Mobilitätsauschuss"
]
},
{
"name": "Jens Thill",
"functions": [
"Mitglied Ausschuss für kommunale Sicherheit und Bevölkerungsschutz"
]
},
{
"name": "Jonas Deichelmann",
"functions": [
"Beisitzer"
]
},
{
"name": "Klaus-Peter Fuhrmann",
"functions": [
"Mitglied Ausschuss für kommunale Sicherheit und Bevölkerungsschutz",
"Mitglied Friedhofsausschuss",
"Mitglied Stadtrechtsausschuss",
"Mitglied Gesellschafterausschuss der Entsorgungsgesellschaft Worms mbH AöR"
]
},
{
"name": "Leon Giegerich",
"functions": [
"Mitglied Regionaltag Rheinhessen",
"Mitglied Digitalisierungsausschuss",
"Mitglied Bau und Mobilitätsauschuss"
]
},
{
"name": "Marco Fruci",
"functions": [
"Pate Jugendparlament",
"Mitglied Haupt und Finanzausschuss",
"Mitglied Kulturausschuss",
"Mitglied Digitalisierungsausschus"
]
},
{
"name": "Marcus Belica",
"functions": [
"Beisitzer"
]
},
{
"name": "Maria Unterschütz",
"functions": [
"Mitglied Bildungs und Schulträgerausschuss",
"Mitglied Friedhofsausschuss",
"Mitglied Haupt und Finanzausschuss",
"Mitglied Innenstadtausschuss",
"Mitglied Sozialausschuss",
"Vorsitzende"
]
},
{
"name": "Markus Trapp",
"functions": [
"Mitglied Sportausschuss",
"Mitglied Umwelt und Agrarausschuss",
"Mitglied Kulturausschuss"
]
},
{
"name": "Monika Fischer",
"functions": [
"Beisitzerin"
]
},
{
"name": "Ralf Lottermann",
"functions": [
"Mitglied Umlegungsausschuss",
"Mitglied Verwaltungsrat der ebwo AöR",
"Mitglied Rechnungsprüfungsausschuss"
]
},
{
"name": "Simone Korte-Bernhard",
"functions": [
"Beisitzerin"
]
},
{
"name": "Timo Horst",
"functions": [
"Vorsitzender"
]
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment