[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81715":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":8,"language":10,"languages":8,"totalLinesOfCode":8,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":15,"stars7d":16,"stars30d":16,"stars90d":14,"forks30d":14,"starsTrendScore":12,"compositeScore":17,"rankGlobal":8,"rankLanguage":8,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":21,"topics":22,"createdAt":8,"pushedAt":8,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":14,"starSnapshotCount":14,"syncStatus":15,"lastSyncTime":26,"discoverSource":27},81715,"nuernberg-maps-review-removals","patlux\u002Fnuernberg-maps-review-removals","patlux",null,"https:\u002F\u002Fnuernberg-maps-review-removals.patwoz.dev\u002F","Go",44,6,1,0,2,4,45.94,"MIT License",false,"main",true,[],"2026-06-12 04:01:35","# Nürnberg Google-Bewertungen: Diffamierungs-Löschbanner\n\nReproduzierbarer lokaler Go-Workflow, um öffentlich sichtbare Google-Maps-Ortsdaten zu sammeln, Hinweise auf entfernte Bewertungen zu erkennen, zum Beispiel:\n\n> „21 bis 50 Bewertungen aufgrund von Beschwerden wegen Diffamierung entfernt.“\n\n…und daraus Nürnberg-Auswertungen sowie ein interaktives Dashboard zu erzeugen.\n\n## Wichtige Hinweise\n\n- Nur für private Recherche \u002F Journalismus gedacht. Google-Maps-Bedingungen und geltendes Recht beachten.\n- Der Scraper speichert nur, was zum Scrape-Zeitpunkt öffentlich sichtbar ist. Manuell geprüfte Abweichungen können als Overrides in `internal\u002Fmapsreview\u002Fdata\u002Fplace_overrides.json` gepflegt werden.\n- Kein Banner ≠ definitiv keine entfernten Bewertungen. Es bedeutet nur: Beim Scrape wurde kein passender sichtbarer Hinweis erkannt.\n- Das angepasste Rating nimmt an, dass alle entfernten Bewertungen 1-Stern-Bewertungen waren. Das ist ein Worst-Case-Modell, keine Tatsache.\n- Langsame Delays verwenden. Wenn Google ein CAPTCHA zeigt: stoppen oder im sichtbaren Browser manuell lösen.\n\n## Einrichtung\n\nVoraussetzungen:\n\n- Go 1.25+\n- Chrome oder Chromium im `PATH` oder an einem Standard-Installationsort\n- Optional als experimentelles CDP-Backend: Lightpanda\n- Optional für Places-API-Discovery: Google Places API (New)-API-Key in `config.toml`\n- Optional für PNG-Export: ImageMagick `magick` oder `convert`\n\n```bash\nmake setup\n# oder direkt:\ngo mod download\n```\n\nFür die optionale Places-API-Discovery `config.toml` aus `.config.toml.example` anlegen und `[places_api].api_key` setzen. `config.toml` bleibt lokal\u002Fgit-ignoriert.\n\n## 1) Daten sammeln\n\nStandardmäßig nutzt der Scraper Chrome. Er liest die normale Google-Maps-Seite für Metadaten und die direkte Rezensionen-URL für Rating, Rezensionszahl und Löschbanner, weil die normale Maps-Ansicht Löschbanner teils nicht im DOM enthält.\n\nDer Workflow ist immer zweistufig:\n\n1. **Discovery** schreibt\u002Ferweitert `output\u002Fdiscovery.json`.\n2. **Scrape** liest `output\u002Fdiscovery.json`, öffnet die Orte in Google Maps im Browser und schreibt `output\u002Fplaces.json` \u002F `output\u002Fplaces.csv`.\n\nDie Löschbanner-Erkennung passiert in beiden Varianten im Browser auf Google Maps. Die Places API wird nur optional für Discovery verwendet.\n\n### Variante A: Discovery ohne Places API\n\nDiese Variante nutzt nur den Browser: Google-Maps-Suchen werden geöffnet, sichtbare Ergebnislinks gesammelt und danach gescrapt.\n\n```bash\n# 1. Orte über Google-Maps-Suchergebnisse finden\nmake scrape ARGS=\"--discovery-only --postcodes all --headless=false\"\n\n# 2. Gefundene Orte im Browser scrapen, inklusive Rezensionen\u002FLöschbanner\nmake scrape ARGS=\"--scrape-only --headless=false\"\n```\n\nVorteile: kein API-Key, keine Google-Cloud-Quota, kein API-Billing-Risiko. Nachteile: langsamer, stärker abhängig von der Google-Maps-Oberfläche und der sichtbaren Ergebnisliste.\n\n### Variante B: Discovery mit Places API\n\nDiese Variante nutzt die offizielle Places API (New) nur für die Ortssuche. Die Text-Search-Anfrage ist bewusst auf ID-only-Felder beschränkt:\n\n```text\nplaces.id,nextPageToken\n```\n\nDanach ist der Ablauf identisch: Die gefundenen `ChIJ...`-Place-IDs werden als Google-Maps-URLs in `output\u002Fdiscovery.json` gespeichert und im Browser gescrapt. Beim Scrape löst Google Maps die URL auf eine kanonische `\u002Fmaps\u002Fplace\u002F...\u002Fdata=...` URL auf; diese wird anschließend in `output\u002Fplaces.json` gespeichert, damit spätere Läufe direktere Maps-URLs\u002FIDs haben.\n\n```bash\n# 1. Orte über Places API Text Search finden\nmake scrape ARGS=\"--places-api-discovery --discovery-only --places-api-pages 1\"\n\n# 2. Gefundene Orte im Browser scrapen, inklusive Rezensionen\u002FLöschbanner\nmake scrape ARGS=\"--scrape-only --headless=false\"\n```\n\nFür tiefere Discovery, wenn die Tagesquota entsprechend gesetzt ist:\n\n```bash\nmake scrape ARGS=\"--places-api-discovery --discovery-only --places-api-pages 2\"\nmake scrape ARGS=\"--scrape-only --headless=false\"\n```\n\nVorteile: bessere und stabilere Discovery-Abdeckung. Nachteile: API-Key, Quota-Management und Billing-Monitoring nötig. Die API liefert keine Löschbanner; dafür bleibt immer der Browser-Scrape nötig.\n\nVollständiger Nürnberg-Lauf mit der Standard-Browser-Discovery:\n\n```bash\nmake scrape ARGS=\"--postcodes all --headless=false\"\n```\n\nKleiner Testlauf:\n\n```bash\nmake scrape ARGS=\"--postcodes 90402 --queries restaurant,café --max-results 20 --headless=false\"\n```\n\nAusgaben:\n\n- `output\u002Fdiscovery.json` — gefundene Google-Maps-Orte\n- `output\u002Fplaces.json` — gescrapte Daten inklusive Koordinaten und, sofern zuordenbar, `bezirkId` \u002F `bezirkName`\n- `output\u002Fplaces.csv` — CSV-Export für Tabellenkalkulationen\n- `output\u002Fmetadata.json` — Scrape-Einstellungen, Zählwerte, Zeitstempel und User-Agent\n\nNützliche Optionen:\n\n```bash\n--city Nürnberg\n--postcodes 90402,90403\n--queries restaurant,café,imbiss,pizzeria,bäckerei\n--discovery-only\n--places-api-discovery --discovery-only   # experimentell: offizielle Places API Text Search, ID-only\u002Fno-cost-SKU laut Google-Preisliste; liest [places_api].api_key aus config.toml\n--places-api-pages 1                       # API-Seiten pro PLZ\u002FSuche; Default 1 hält die Standardsuchen unter 1.000 Requests\u002FTag\n--scrape-only\n--scrape-only --rescrape-all   # alle gefundenen Orte erneut lesen, auch bereits erfolgreiche\n--scrape-only --rescrape-all --allow-banner-clears   # zuvor erkannte Banner nach manueller Prüfung entfernen lassen\n--scrape-only --banner-audit-only --notice-attempts 2   # no-banner-Zeilen gezielt auf übersehene Banner prüfen; schreibt nur neu gefundene Banner\n--scrape-only --rescrape-all --resume-from 1288   # vollständigen Rescan an 1-basierter Todo-Position fortsetzen\n--scrape-only --rescrape-all --resume-from 1288 --scrape-limit 200   # sichereren Teil-Scan ausführen\n--delay-min 4000 --delay-max 9000\n--out output\u002Fplaces.json --csv output\u002Fplaces.csv\n--discovery output\u002Fdiscovery.json --metadata output\u002Fmetadata.json\n```\n\n### Andere Städte mit `--city`\n\n`--city` ist für Scrape, Diagramme und Dashboard verfügbar. Es setzt den Ortsnamen für die Google-Maps-Suche, die Dashboard-Texte\u002FSEO-Metadaten und die Diagramm-Dateipräfixe.\n\nWichtig: Für jede Nicht-Nürnberg-Stadt muss `--postcodes` explizit als CSV übergeben werden; `--postcodes all` ist dort absichtlich ungültig, damit nicht versehentlich die Nürnberger Standard-PLZ genutzt werden.\n\n```bash\n# Beispiel: Fürth-Orte suchen und scrapen\nmake scrape ARGS=\"--city Fürth --postcodes 90762,90763 --queries restaurant,café --headless=false\"\n\n# Diagramme mit City-Slug als Dateipräfix erzeugen, z. B. fuerth_overall_summary.svg\nmake charts ARGS=\"--city Fürth --png\"\n\n# Dashboard-Texte und Metadaten auf Fürth setzen\ngo run .\u002Fcmd\u002Fdashboard --city Fürth --output output\u002Fcharts\u002Ffuerth_dashboard.html\n```\n\nEinschränkungen: Die statistischen Bezirke und Kartenflächen sind Nürnberg-spezifisch. Bei anderen Städten zeigt das Dashboard keine Nürnberger Bezirksflächen; `go run .\u002Fcmd\u002Fvalidate --strict-nuremberg` ist dann nicht passend. Für eigene Stadt-Domains `city`, `output`, `site_domain`, `site_url` und `site_output` in `config.toml` anpassen.\n\nOptional kann der Scraper über CDP gegen einen bereits laufenden Browser wie Lightpanda laufen. Das ist experimentell; Chrome bleibt der Standard und war in Stichproben schneller:\n\n```bash\nLIGHTPANDA_DISABLE_TELEMETRY=true lightpanda serve --host 127.0.0.1 --port 9333\nmake scrape ARGS=\"--scrape-only --rescrape-all --cdp-url ws:\u002F\u002F127.0.0.1:9333 --save-every 25 --delay-min 4000 --delay-max 9000\"\n```\n\nLightpanda ist als Vergleichs- oder Fallback-Backend nützlich, sollte aber mit Stichproben gegen Chrome geprüft werden, bevor seine Ergebnisse übernommen werden.\n\nNach einem Refresh kann ein konservativer Banner-Audit helfen, zuvor übersehene Löschbanner zu finden. Der Audit prüft nur bestehende erfolgreiche Zeilen ohne Banner und schreibt ausschließlich neu gefundene Banner; bestehende Banner werden dabei nie entfernt:\n\n```bash\nmake scrape ARGS=\"--scrape-only --banner-audit-only --notice-attempts 2 --save-every 25 --delay-min 4000 --delay-max 9000\"\n```\n\n## 2) Datenqualität verbessern\n\nFehlende Adressen nachtragen:\n\n```bash\nmake backfill ARGS=\"--headless=true --concurrency 4\"\n```\n\nScrape-Ergebnis validieren:\n\n```bash\nmake validate\ngo run .\u002Fcmd\u002Fvalidate --strict-nuremberg\n```\n\nDie Validierung meldet fehlende Adressen, fehlende Ratings\u002FRezensionszahlen, fehlende Nürnberg-Bezirkszuordnungen, Nicht-Nürnberger Postleitzahlen, doppelte URLs\u002FIDs und Banner-Zeilen mit Parse-Problemen.\n\n## 3) Diagramme und Dashboard erzeugen\n\n```bash\nmake charts ARGS=\"--png\"\nmake dashboard\n```\n\n### Dashboard-Konfiguration\n\nOptional kann `cmd\u002Fdashboard` eine lokale TOML-Konfiguration laden. Wenn `config.toml` im Projektroot existiert, wird sie automatisch verwendet; alternativ kann ein Pfad explizit übergeben werden:\n\n```bash\ncp .config.toml.example config.toml\n$EDITOR config.toml\n\ngo run .\u002Fcmd\u002Fdashboard --config config.toml\n# oder implizit, wenn config.toml existiert:\ngo run .\u002Fcmd\u002Fdashboard\n```\n\n`config.toml` ist git-ignoriert, weil darin rechtliche Kontakt-\u002FAdressdaten stehen können. CLI-Flags überschreiben Werte aus der Konfiguration.\n\nBeispiel:\n\n```toml\ncity = \"Nürnberg\"\ninput = \"output\u002Fplaces.json\"\noutput = \"output\u002Fcharts\u002Fnuernberg_dashboard.html\"\nsite_domain = \"nuernberg-maps-review-removals.patwoz.dev\"\nsite_url = \"https:\u002F\u002Fnuernberg-maps-review-removals.patwoz.dev\"\nsite_output = \"public\"\n\n[legal]\nenabled = true\nname = \"Patrick Wozniak\"\nemail = \"hi@example.com\"\naddress_lines = [\n  \"c\u002Fo Example GmbH\",\n  \"Example Street 1\",\n  \"90443 Nürnberg\",\n  \"Deutschland\",\n]\nnote = \"Example GmbH ist nicht Betreiberin dieses Angebots. Sie dient ausschließlich als Zustellanschrift.\"\npost_handler = \"Example GmbH\"\n\n[analytics]\n# Optional: Plausible Analytics. Abschnitt entfernen, um Analytics zu deaktivieren.\nsrc = \"https:\u002F\u002Fa.patwoz.dev\u002Fjs\u002Fscript.js\"\ndomain = \"nuernberg-maps-review-removals.patwoz.dev\"\n\n[places_api]\n# Optional. Nur für --places-api-discovery erforderlich.\napi_key = \"\"\n```\n\nWenn `[legal]` fehlt oder `enabled = false` gesetzt ist, erzeugt das Dashboard keine `methodik.html`, `korrektur.html`, `impressum.html` und `datenschutz.html` und verlinkt diese Seiten nicht. Sobald Legal-Seiten aktiviert sind, müssen `name`, `email` und `address_lines` gesetzt sein.\n\nRechtlicher Hinweis: Die erzeugten Impressum-\u002FDatenschutz-\u002FMethodik-Texte sind technische Vorlagen und keine Rechtsberatung. Prüfe vor Veröffentlichung, ob Verantwortlicher, ladungsfähige Anschrift, E-Mail, Zustellanschrift, Hosting, Analytics, Kartenanbieter und sonstige Dienste für dein konkretes Setup korrekt und vollständig beschrieben sind. Eine c\u002Fo- oder Zustellanschrift sollte nur verwendet werden, wenn dort rechtliche Post zuverlässig entgegengenommen und an dich weitergeleitet wird.\n\nAusgaben:\n\n- `output\u002Fcharts\u002Fnuernberg_dashboard.html` — interaktive App mit KPIs, Filtern, Karte, sortierbarer Explorer-Tabelle und Google-Maps-Links\n- `output\u002Fcharts\u002Fnuernberg_overall_summary.svg\u002F.png`\n- `output\u002Fcharts\u002Fnuernberg_90402_summary.svg\u002F.png`\n- `output\u002Fcharts\u002Fnuernberg_most_removed.csv`\n- `output\u002Fcharts\u002Fnuernberg_most_removed.md`\n- `output\u002Fcharts\u002Fnuernberg_most_removed.html`\n\nWenn ImageMagick nicht installiert ist, überspringt `--png` die PNG-Dateien und schreibt weiterhin SVGs.\n\nDie erzeugten Diagramm- und Dashboard-Dateien unter `output\u002Fcharts\u002F` werden von git ignoriert. Im Repository bleiben nur die Scrape-Snapshots (`output\u002Fplaces.json`, `output\u002Fplaces.csv`, `output\u002Fmetadata.json`, optional `output\u002Fdiscovery.json`) versioniert; `make site` ruft `cmd\u002Fdashboard --site` auf und baut daraus `public\u002F` für GitHub Pages. `site_domain`, `site_url` und `site_output` kommen dabei ausschließlich aus `config.toml`.\n\nDie Dashboard-Karte nutzt Leaflet mit CARTO-Kartenkacheln auf Basis von OpenStreetMap-Daten. Beim Öffnen der HTML-Datei ist deshalb Internetzugriff für Kartenkacheln nötig. Das Dashboard gruppiert, filtert und überlagert Einträge außerdem nach Nürnberger statistischem Bezirk (`Bezirk`).\n\n## Veröffentlichung mit GitHub Pages\n\nGitHub Pages ist auf den Branch `gh-pages` konfiguriert. Der Branch enthält nur das generierte `public\u002F`-Artefakt; die Quell- und Snapshot-Dateien bleiben auf `main`.\n\nÖffentliche URL: \u003Chttps:\u002F\u002Fnuernberg-maps-review-removals.patwoz.dev\u002F>\n\nLokale Vorschau des Veröffentlichungs-Artefakts:\n\n```bash\nmake site\npython3 -m http.server --directory public 8080\n```\n\nOptionale Plausible-Analytics werden eingebunden, wenn sie in `config.toml` unter `[analytics]` stehen.\n\nFür GitHub Actions muss der Inhalt der lokalen `config.toml` als Repository-Secret `DASHBOARD_CONFIG_TOML` hinterlegt werden. Der Workflow schreibt daraus zur Laufzeit eine temporäre `config.toml`; der Inhalt wird nicht ins Repository oder nach `gh-pages` kopiert. Alternativ funktioniert auch eine Repository-Variable gleichen Namens, falls die Daten nicht geheim sind.\n\nVeröffentlichen:\n\n```bash\nmake deploy-pages\n```\n\nIm GitHub-Repository muss dafür **Settings → Pages → Source: Deploy from a branch**, Branch `gh-pages`, Ordner `\u002F` aktiv sein.\n\n## GitHub Actions\n\nDer Workflow `.github\u002Fworkflows\u002Frefresh-and-deploy.yml` baut und veröffentlicht GitHub Pages bei jedem Push auf `main` neu.\n\nEin Daten-Refresh läuft bewusst nur manuell über **Actions → Refresh data and deploy site → Run workflow** mit aktivierter Option `refresh_data`. Standardmäßig wird dann der vorhandene Discovery-Snapshot komplett neu gescrapt:\n\n```bash\n--scrape-only --rescrape-all --save-every 25 --delay-min 4000 --delay-max 9000 --headless=true\n```\n\nFalls Google ein CAPTCHA oder eine eingeschränkte Ansicht ausliefert, kann der Action-Lauf fehlschlagen oder unvollständige Daten liefern; dann lokal mit sichtbarem Browser neu laufen lassen. Zuvor erkannte Löschbanner werden bei automatischen Re-Scrapes standardmäßig nicht entfernt; dafür ist nach manueller Prüfung `--allow-banner-clears` nötig.\n\n## Tests \u002F Checks\n\n```bash\nmake test\nmake check\n# oder direkt:\ngo test .\u002F...\ngo run .\u002Fcmd\u002Fvalidate\n```\n\n## Was die Diagramme zeigen\n\n1. **Höchste Lösch-Quote**  \n   `removed_midpoint \u002F (visible_reviews + removed_midpoint)`\n\n2. **Schlechtestes „echtes“ Rating**  \n   Annahme: Jede entfernte Bewertung war eine 1-Stern-Bewertung.\n\n3. **Beste Orte ohne Löschbanner**  \n   Ohne sichtbaren Diffamierungs-Löschbanner, sortiert nach Rating und danach Rezensionszahl.\n\n4. **Verteilung der Lösch-Stufen**  \n   Zählt Orte nach Googles sichtbaren Löschbereichen.\n\n## Nürnberger statistische Bezirke\n\nEinträge mit Koordinaten werden über die offizielle Bezirksatlas-Geometrie von `online-service2.nuernberg.de\u002Fgeoinf\u002Fia_bezirksatlas\u002F` den Nürnberger statistischen Bezirken zugeordnet. Die Geometrie liegt in `internal\u002Fmapsreview\u002Fdata\u002Fnuernberg_statistische_bezirke.json`.\n\nPunkte in nicht bewohnten Lücken dieser Quelle werden nur dann dem nächstgelegenen statistischen Bezirk zugeordnet, wenn die Zeile eine Nürnberger Postleitzahl hat. Nicht-Nürnberger Postleitzahlen bleiben ohne Bezirkszuordnung.\n\n## Standardmäßig enthaltene Nürnberger PLZ\n\n`90402, 90403, 90408, 90409, 90411, 90419, 90425, 90427, 90429, 90431, 90439, 90441, 90443, 90449, 90451, 90453, 90455, 90459, 90461, 90469, 90471, 90473, 90475, 90478, 90480, 90482, 90489, 90491`\n\n## Hinweise zur Vollständigkeit\n\nDie Google-Maps-Suche ist kein vollständiger Datenbankexport. Für bessere Abdeckung mehrere Suchbegriffe pro PLZ verwenden und Ergebnisse deduplizieren. Die Standard-Suchbegriffe sind:\n\n`restaurant, café, imbiss, pizzeria, bäckerei, döner, burger, sushi, schnitzel, frühstück, brunch`\n\nFür einen strengeren „nur Restaurants“-Datensatz nur `--queries restaurant` verwenden und `output\u002Fplaces.csv` anschließend manuell filtern.\n\n## Lizenz\n\nMIT, siehe [`LICENSE`](LICENSE).\n","该项目旨在通过收集公开可见的Google Maps地点数据，识别并记录因诽谤投诉而被删除的评论信息，并生成纽伦堡地区的分析报告及交互式仪表板。项目采用Go语言开发，利用浏览器自动化技术抓取Google Maps上的地点信息与删除评论提示，支持两种数据发现模式：一种是基于纯浏览器操作，另一种则可选地结合了Google Places API以提高效率和准确性。它适用于私人研究或新闻调查场景，在遵守相关法律法规的前提下，帮助用户了解特定区域内的在线评价真实性情况。","2026-06-11 04:06:06","CREATED_QUERY"]