Skip to content

About

Findet Betriebe mit Bedarf an einem bestimmten Thema und sortiert die aus, die nur so aussehen. OpenStreetMap + höflicher Crawl, Auswertung als eigenständige HTML-Datei.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Marktradar

Findet Betriebe, bei denen sich dein Angebot lohnt — und sortiert die aus, die nur so aussehen.

Der Radar holt eine Firmenliste aus OpenStreetMap, liest von jeder Firma die Startseite plus Karriere-, Technik- und Über-uns-Seite und bewertet, ob dort gerade Bedarf für dein Thema entsteht. Ergebnis ist eine durchsuchbare HTML-Auswertung mit Direktlinks auf Website und Karriereseite.

config.toml ──▶ seed_from_osm.py ──▶ companies.csv ──▶ marktradar.py ──▶ results.csv ──▶ report.html

Die Auswertung: Kennzahlen, stärkster Bedarf, häufigste Fundstellen und die vollständige Firmentabelle

Die Auswertung — eine eigenständige HTML-Datei, durchsuchbar und verschickbar. Sämtliche Daten hier sind simuliert.

Aufbau

├── run.sh              alle Befehle
├── config.toml         Beispiel-Konfiguration (Region + Suchbegriffe)
├── src/                der Code
├── privat/             deine Daten – wird nie versioniert
│   ├── config.toml       deine eigene Konfiguration
│   ├── companies.csv     gefundene Firmen
│   ├── results.csv       Bewertungen
│   ├── report.html       Auswertung
│   ├── exclude.txt       Ausschlussliste
│   └── .env              Zugangsdaten
└── Dockerfile, docker-compose.yml

Alles Persönliche liegt in einem Ordner. privat/ steht in .gitignore, du kannst das Repo also ohne weitere Prüfung veröffentlichen. Existiert dort keine eigene config.toml, greift die Beispiel-Konfiguration aus dem Projektstamm — ein frischer Klon läuft damit sofort.

Das mitgelieferte Beispiel sucht Fertigungsbetriebe mit Bedarf an MES/MDE/BDE-Systemen. Für ein anderes Thema änderst du eine Datei, siehe Auf dein Thema anpassen.

Loslegen

./run.sh setup     # Einrichtung im Browser  -> config.toml
./run.sh seed      # Firmen aus OpenStreetMap -> companies.csv
./run.sh radar     # crawlen und bewerten    -> results.csv + report.html
./run.sh top 10    # Kurzliste im Terminal

run.sh legt die virtuelle Umgebung beim ersten Start selbst an und lädt .env. Kein source .venv/bin/activate nötig, kein API-Key.

Befehl Was passiert
./run.sh setup Einrichtung im Browser öffnen
./run.sh seed Firmen aus OpenStreetMap holen, companies.csv ergänzen
./run.sh seed --dry-run nur zeigen, was dazukäme
./run.sh seed --replace Liste neu bauen statt ergänzen
./run.sh seed --region <name> nur eine Region aus config.toml
./run.sh seed-search zusätzlich über eine SearXNG-Instanz suchen
./run.sh seed-places zusätzlich über Google Places (kostet, Key nötig)
./run.sh radar alle Firmen crawlen → results.csv + report.html
./run.sh resume abgebrochenen Lauf fortsetzen, nur Neuzugänge crawlen
./run.sh report Auswertung neu bauen und öffnen
./run.sh top [N] Kurzliste im Terminal (Default 10)
./run.sh all seed (falls nötig) → radar → top

Nach jedem seed willst du resume, nicht radar — radar fängt von vorn an, resume crawlt nur die neuen Firmen.

Die Oberfläche: Konfiguration, Firmenliste und Läufe an einer Stelle

Die Oberfläche unter http://127.0.0.1:8765: Region festlegen, Firmenliste pflegen, Läufe starten und live mitlesen. Auch hier simulierte Daten.

Mit Docker

Läuft komplett lokal, öffnet keinen Port.

docker compose up -d --build     # startet die Oberfläche
open http://127.0.0.1:8765       # konfigurieren, Läufe starten, mitlesen
docker compose down              # beenden

Der Container arbeitet in ./privat — demselben Ordner wie run.sh. Lokal und im Container siehst du also immer denselben Stand.

Das ist der bequemste Weg: der Container startet die Weboberfläche, und von dort erledigst du alles. Der Port ist in docker-compose.yml an 127.0.0.1 gebunden — auch aus dem Container heraus ist nichts aus dem Netzwerk erreichbar.

Einzelne Läufe gehen weiterhin über die Kommandozeile:

docker compose run --rm marktradar seed
docker compose run --rm marktradar radar

Einzelne Läufe gehen auch ohne Oberfläche:

docker compose run --rm marktradar seed
docker compose run --rm marktradar radar

Alle Dateien liegen in ./privat auf deinem Rechner und überleben jeden Container-Neustart. Fehlt dort eine config.toml, wird die Beispiel-Datei hineinkopiert — ändern kannst du sie im Editor, ohne das Image neu zu bauen. Zusätzlich gibt es shell für eine Shell im Container.

Auf dein Thema anpassen

Eine Datei: privat/config.toml. Kein Python, kein Code. Sie beantwortet zwei Fragen — wo suchst du, und wonach. Als Vorlage dient config.toml im Projektstamm; die bleibt unangetastet.

Im Browser einrichten und laufen lassen

./run.sh setup

Öffnet http://127.0.0.1:8765. Ein winziger Server aus der Standardbibliothek, der nur auf 127.0.0.1 lauscht — aus dem Netzwerk ist nichts erreichbar.

Er lädt deine bestehende config.toml, und Speichern schreibt sie direkt zurück (vorher als .bak gesichert). Kein Herunterladen und Verschieben.

Zugangsdaten trägst du dort ebenfalls ein — Kontakt für den Crawler, Google-Key, SearXNG-Adresse. Sie landen in privat/.env mit Dateirechten 600, nicht in der config.toml: die ist zum Teilen und Versionieren gedacht, ein Schlüssel darin landet früher oder später im Repo. Der Key verlässt den Server nie wieder, angezeigt wird nur AIza…7890.

Die Firmenliste bearbeitest du dort ebenfalls. Haken weg heißt: raus aus companies.csv. Zusätzlich ausschließen heißt: kommt auch beim nächsten Suchlauf nicht zurück — die Domain landet in exclude.txt. Ohne das würde der nächste seed die Firma wieder einsammeln. Sammelaktionen: nur Leads behalten, Aussortierte wegwerfen, alles/nichts — jeweils bezogen auf den aktiven Filter. Vor jedem Speichern wird companies.csv.bak angelegt, und eine komplett leere Liste lehnt der Server ab.

Änderungen an der Konfiguration speichern sich selbst, gut eine Sekunde nachdem du aufhörst zu tippen — die Statuszeile nennt die Uhrzeit. Eine Sicherung entsteht einmal je Serverstart als config.toml.bak; bei jedem Tastendruck eine neue anzulegen wäre wertlos.

Die Läufe startest du aus derselben Seite: Firmen suchen, Crawlen, Fortsetzen, Auswertung. Fortschrittsbalken, Firmenname und Live-Log laufen mit, Abbrechen stoppt sauber — der Zwischenstand bleibt erhalten.

Nach einem erfolgreichen Crawl baut sich die Auswertung von selbst; ein Knopf öffnet sie danach. Sonst zeigte report.html den Stand von vorher, und man müsste daran denken.

Dahinter stecken Server-Sent Events, kein WebSocket: Fortschritt fließt nur in eine Richtung (Server → Browser), das ist gewöhnliches HTTP ohne Handshake, ohne Framing, ohne Bibliothek — und der Browser verbindet sich nach einem Abbruch von selbst neu. Schließt du den Tab mitten im Lauf, läuft er weiter; beim nächsten Öffnen ist der Stand wieder da.

Gestartet werden können nur die vier fest hinterlegten Läufe — aus der Oberfläche lässt sich nichts anderes ausführen.

Die Region gibst du als Ortsname plus Umkreis an, die Koordinaten rechnet die Seite aus — dafür fragt sie OpenStreetMap, sonst geht nichts nach außen. Ungültiges TOML wird abgewiesen, bevor etwas überschrieben wird.

Ohne Server (setup.html direkt im Browser geöffnet) funktioniert alles außer dem Speichern; dort gibt es dann Herunterladen und Bestehende laden.

Wer lieber im Editor arbeitet, ändert config.toml direkt — die Datei ist kommentiert und bleibt die einzige Quelle.

Wo

[[regions]]                          # beliebig viele Blöcke
name = "Franken/Oberpfalz"
bbox = [49.2, 10.4, 50.4, 12.2]      # [süd, west, nord, ost]

[[regions]]
name = "Schwaben"
bbox = [48.2, 9.8, 48.8, 11.0]

Die Ecken liest du auf openstreetmap.org ab. Mehrere Regionen dürfen weit auseinanderliegen; jede wird einzeln gecacht, du kannst also eine ändern, ohne die anderen neu abzufragen. ./run.sh seed --region schwaben nimmt nur eine.

Für die Suchmaschinen-Seeder zusätzlich:

[search]
branchen = ["Kunststoffverarbeitung Spritzguss", "Werkzeugbau Formenbau", ...]
orte     = ["Forchheim", "Erlangen", ...]

Abgefragt wird jede Branche in jedem Ort — 10 × 10 ergibt 100 Anfragen.

Wonach

Fünf Begriffslisten, jede mit einem Gewicht:

Liste Frage
[signals.fit] Ist das überhaupt die Art Betrieb, die du bedienst?
[signals.invest] Reden sie öffentlich über dein Thema?
[signals.job] Suchen sie gerade Leute dafür?
[signals.tech] Womit arbeiten sie? (schwacher Kontext)
[signals.off_target] Verkaufen sie das selbst? (Handel, Wettbewerb)

Dazu [thresholds] für die Einstufung, [crawl] für Tempo und Seitenzahl und [page_hints] für die Unterseiten, die gelesen werden.

Alles klein schreiben. Wortgrenzen werden beachtet, mes trifft also nicht in „Messtechnik". Fehler in der Datei melden sich beim Start mit Klartext und Zeilennummer statt mit einem Stacktrace.

Fertige Vorlagen

Im Ordner vorlagen/ liegen komplette Konfigurationen: Fertigung, Lebensmittelherstellung, Elektro/SHK-Handwerk, Logistik, Kfz-Werkstätten. Kopieren, Region setzen, loslegen:

cp vorlagen/lebensmittel.toml privat/config.toml

Jede Vorlage beschreibt zwei Achsen, die man getrennt tauschen kann: die Branche (wen du ansprichst — [osm] selectors und [signals.fit]) und dein Angebot (was du verkaufst — [signals.invest] und [signals.job]). Alle mitgelieferten Vorlagen haben dasselbe Angebot: Produktions- und Prozess-IT. Wer derselben Branche etwas anderes verkauft, tauscht nur die beiden Angebots-Listen. Details in vorlagen/README.md.

Für welche Branche auch immer

Der übrige Code weiß nichts über Fertigung, MES oder Franken. Drei Dinge machen dein Thema aus, alle drei stehen in der config.toml:

[osm]
selectors = [                        # was überhaupt als Betrieb gilt
  '["tourism"="hotel"]',
  '["amenity"="restaurant"]',
]

Die Vorgabe sucht Fabriken, Handwerks- und Industriebetriebe. Für andere Branchen tauschst du die Filter — Beispiele stehen als Kommentar in der Datei:

Zielgruppe Filter
Hotels, Gastronomie ["tourism"="hotel"], ["amenity"="restaurant"]
Arzt- und Zahnarztpraxen ["amenity"="doctors"], ["amenity"="dentist"]
Pflegeheime ["amenity"="nursing_home"], ["social_facility"]
Autohäuser, Werkstätten ["shop"="car"], ["shop"="car_repair"]
Logistik, Lager ["landuse"="industrial"]["name"]
Landwirtschaft ["landuse"="farmyard"]["name"]

Dazu die Begriffslisten und die Region — fertig. Alles auch in der Oberfläche unter Feineinstellung pflegbar.

Wo es an Grenzen stößt: OpenStreetMap kennt Betriebe mit Adresse. Reine Online-Geschäfte, Agenturen oder Beratungen ohne Standort stehen dort selten mit Website. Für solche Zielgruppen ist der SearXNG-Seeder der bessere Weg — oder du pflegst companies.csv von Hand, zwei Spalten name,domain.

Warum vier Fragen statt einer

Die naheliegende Variante — „zähl, wie oft die über mein Thema reden" — funktioniert nicht. Wer am lautesten über ein Thema redet, ist meist der Anbieter, nicht der Kunde. In der ersten Version dieses Projekts stand ein Elektrogroßhandel ganz oben, weil er Digitalisierung verkauft.

Deshalb ist fit die Voraussetzung: ohne passendes Betriebsprofil zählt kein noch so hoher Bedarfs-Score. Und off_target kennzeichnet Handel, Systemhäuser und Wettbewerb.

Umgekehrt gilt: Ein passender Betrieb ohne jedes Signal ist kein schlechter Lead, sondern Greenfield — da hat noch niemand angefangen. Solche Firmen haben oft nur eine dünne Website. Deshalb zählt auch der Firmenname beim Profil mit: „Musterfirma Kunststofftechnik" sagt schon alles, selbst wenn die Seite aus drei Sätzen besteht.

Die Einstufungen

Einstufung Bedeutung Was du damit machst
Aktiver Bedarf investiert gerade oder sucht Leute dafür direkt ansprechen, Karriereseite ist der Aufhänger
Thema angefasst erste Signale beobachten, in ein paar Monaten nachfassen
Unbeackert passender Betrieb, kein Signal Greenfield, nach Größe priorisieren
Vermutlich kein Kunde Handel, Wettbewerb, Profil passt nicht aussortiert

Gezählt werden verschiedene Begriffe, nicht Treffer pro Seite — sonst bekäme eine Firma mit sechs lesbaren Unterseiten automatisch das Sechsfache einer mit nur einer, und der Score misst die Website-Größe statt des Signals. Zusätzlich wird die Mitarbeiterzahl aus dem Text gezogen, wo sie dort steht.

Firmen finden

OpenStreetMap (Standard, empfohlen)

./run.sh seed fragt die Overpass-API ab: kostenlos, kein Key, ausdrücklich für Massenabfragen gedacht. Eine Abfrage liefert alle Betriebe der Region, die in OSM eine Website hinterlegt haben — Fabriken (man_made=works), Handwerks- und Industriebetriebe, Firmensitze mit Branchenangabe.

Vorgefiltert wird über dieselben fit-Begriffe wie beim Scoring, damit keine Bäckereien in der Crawl-Liste landen; --all nimmt alles mit. Regionen über [[regions]] in config.toml.

Die Haupt-Instanz overpass-api.de ist oft überlastet — der Seeder probiert vier Server durch. Die Antwort landet in .osm_cache.json, --refresh fragt neu ab.

SearXNG (ergänzend, mitgeliefert)

docker compose up startet eine eigene SearXNG-Instanz mit. Sie ist nur im internen Docker-Netz erreichbar — kein Port nach außen, du belastest keine fremde Instanz und keine fremde dich. ./run.sh seed-search nutzt sie automatisch.

Ohne Docker setzt du SEARXNG_URL auf deine eigene Instanz. Öffentliche Instanzen sperren die JSON-API meist ohnehin ab.

Erwarte davon nicht zu viel. SearXNG fragt echte Suchmaschinen ab, und die drosseln hart: nach rund 15 Anfragen antwortet DuckDuckGo mit CAPTCHA, danach kommen null Treffer. Der Seeder erkennt das und bricht mit Klartext ab, statt stumm leere Ergebnisse zu liefern. Verlässlich ist OpenStreetMap.

Nur geprüfte Engines sind aktiv (SEARXNG_ENGINES, Standard duckduckgo). Bing lieferte auf „Werkzeugbau Formenbau Erlangen" Uhrzeiten und Kochrezepte — wer weitere Engines ergänzt, sollte sie stichprobenartig prüfen.

Portale und Jobbörsen (wlw.de, gelbeseiten, jooble, StepStone, LinkedIn …) fliegen automatisch raus, sonst bestünde die halbe Liste daraus. Der Firmenname wird aus dem Seitentitel geschält, ein Teil mit Rechtsform bevorzugt.

Das Delay steht auf 6 Sekunden. Das ist kein Übervorsicht: Bei 1 Sekunde sperren DuckDuckGo, Brave und Startpage die Instanz binnen Minuten mit CAPTCHA — und zwar für alle ihre Nutzer. Der Seeder erkennt das und bricht mit Klartext ab, statt stumm leere Ergebnisse zu liefern.

Google Places (optional)

./run.sh seed-places braucht einen Key in .env und Places API (New) im Cloud-Projekt (die alte „Places API" reicht nicht). Kostet rund 32 USD je 1000 Anfragen; der Seeder zeigt vorher, wie viele Anfragen neu sind und was sie kosten. Antworten landen in .places_cache.json.

Bei allen Seedern gilt: companies.csv wird ergänzt, nicht ersetzt, bei null Treffern nie überschrieben, und vorher als .bak gesichert.

Firmen dauerhaft ausschließen

Eine Domain pro Zeile in exclude.txt (Vorlage: exclude.txt.example):

ehemaliger-arbeitgeber.de
bestandskunde.de

Alle Seeder und der Radar lesen die Datei. Ausgeschlossene Firmen landen weder in companies.csv noch in results.csv — auch nicht nach einem neuen Seed-Lauf. Sie nur aus der CSV zu löschen reicht also nicht. www. ist egal, Subdomains sind mit abgedeckt (firma.de schließt auch shop.firma.de aus), # leitet einen Kommentar ein.

Optik

Beide Seiten tragen dieselbe Optik: dunkler Grund mit feinem Raster, Haarlinien statt Flächen, Eckwinkel an den Rahmen, durchgehend Monospace, Zahlen groß und ruhig. Farbe trägt Bedeutung — Blau für Rubriken und Werte, Grün für Zustände, Rot für Fehler.

Die Gestaltung steht komplett in src/theme.css und wird beim Ausliefern in die Seiten geschrieben. Farben änderst du dort, an einer Stelle, für beide Seiten.

Die Auswertung

./run.sh report baut aus results.csv eine einzelne HTML-Datei und öffnet sie. Kein Server, kein CDN, keine Internetverbindung nötig — du kannst sie so wie sie ist per Mail verschicken.

Drin sind Kennzahlen, die stärksten Firmen und häufigsten Signalbegriffe als Diagramm, darunter die vollständige Tabelle: durchsuchbar nach Firma, Domain und Signalwort, filterbar nach Einstufung, sortierbar per Klick. Filter und Suche wirken auf Diagramme und Tabelle gleichzeitig. Jede Zeile verlinkt Website und Karriereseite. Hell/Dunkel folgt dem System, der Knopf oben rechts überschreibt es.

Lange Läufe

results.csv wird nach jeder Firma neu geschrieben, atomar über eine .tmp-Datei. Ein Abbruch kostet höchstens die gerade laufende Firma; ./run.sh resume macht dort weiter, überspringt Fertiges und versucht Fehlerfälle erneut. Eine einzelne kaputte Seite beendet den Lauf nicht — der Fehler landet als Text in der Spalte error.

Rechne mit rund 15 Sekunden pro Firma — geteilt durch die Zahl der gleichzeitigen Arbeiter. workers in config.toml steht auf 6, gemessen etwa 3,5× schneller als seriell bei identischem Ergebnis.

Parallel bearbeitet werden verschiedene Firmen, nie mehrere Seiten derselben Domain: die Pause von 1,5 s gilt weiterhin pro Domain, fremde Server sehen also unverändert einen gemächlichen Besucher. Wer noch schneller will, senkt max_pages_per_site — das kostet aber Signalqualität, weil die Karriereseite dann eher wegfällt. -j 1 erzwingt strikt seriell.

Fairness und Recht

Der Radar liest öffentlich zugängliche Firmenwebsites — dasselbe, was ein Mensch beim Durchklicken sähe. Er respektiert robots.txt, macht 1,5 s Pause zwischen Anfragen, liest maximal 6 Seiten pro Domain und nennt sich im User-Agent beim Namen. Trage deinen Kontakt in CRAWL_CONTACT ein, damit man dich erreichen kann.

Erfasst werden Firmendaten, keine Personendaten — Ansprechpartner werden nicht ausgelesen. Für die Ansprache gelten die üblichen Regeln (UWG §7, DSGVO): B2B-Telefonakquise braucht mutmaßliche Einwilligung, E-Mail in der Regel eine ausdrückliche. Das ist keine Rechtsberatung.

Overpass ist für Massenabfragen gedacht, SearXNG-Instanzen sind es nicht.

Veröffentlichen

.gitignore hält alles Persönliche draußen. Vor dem ersten Push kontrollieren:

git status --short   # nichts aus privat/ darf auftauchen

Es genügt die eine Regel privat/ — dort liegt alles Persönliche: Firmenliste, Ergebnisse, Ausschlussliste (sie verrät ehemalige Arbeitgeber und Kunden), .env mit Keys und die Caches. Zusätzlich greifen Muster wie *.csv als Sicherheitsnetz, falls doch mal etwas im Projektstamm landet.

Im Repo landen Code, Beispieldateien und diese Doku — 22 Dateien.

Fragen, Fehler, Ideen

Über Issues — auch für „das verstehe ich nicht" oder „bei meiner Branche funktioniert das nicht". Gerade die Begriffslisten leben davon, dass jemand aus einem anderen Fach draufschaut.

Pull Requests sind willkommen. Wer die Signalbegriffe für eine andere Branche zusammenstellt, hilft anderen mehr als jede Codeänderung: leg dafür einfach eine Datei nach dem Muster der config.toml bei.

Wer das gebaut hat

Sascha Böhm Software & App — MDE-, BDE- und ERP-Lösungen für produzierende Betriebe, entwickelt von einem Praktiker mit über 30 Jahren Produktionserfahrung.

Über 30 Jahre in der Fertigung: vom Kunststoff-Formgeber über die technische Leitung bis zur IT-Leitung einer Unternehmensgruppe. Sieben eigenentwickelte MES/MDE/BDE-Systeme laufen bei Kunden im Produktivbetrieb, das älteste seit 2009 — durchgängig open-source-basiert und on-premise.

Schwerpunkte: Fertigungssteuerung und Betriebsdatenerfassung, papierlose Fertigung, Maschinenanbindung über OPC UA und MQTT, ERP-Integration (ERPNext/Frappe, SAP), Workflow-Automatisierung und KI-gestützte Auswertungen.

website.industrie-4-0.org · service@industrie-4-0.org · +49 151 155 20 344 · Hohenschwärz 66, 91322 Gräfenberg

Zwei frei zugängliche Demosysteme zeigen die Arbeitsweise unmittelbar (alle Daten simuliert): mde.dms-iot.de für Maschinendatenerfassung mit OEE, Störungen und Schichtbilanzen, terminals.dms-iot.de für das Maschinenterminal im Schichtbetrieb.

Lizenz

MIT, siehe LICENSE. Die Signatur am Seitenfuß darfst du in einem Fork entfernen — über eine Erwähnung freue ich mich trotzdem.

About

Findet Betriebe mit Bedarf an einem bestimmten Thema und sortiert die aus, die nur so aussehen. OpenStreetMap + höflicher Crawl, Auswertung als eigenständige HTML-Datei.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages