Created
December 11, 2025 16:39
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [ | |
| { | |
| "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