GoodFirms ist ein B2B-Verzeichnis, das Käufer mit IT-Dienstleistern, Softwareunternehmen und Agenturen verbindet. Jedes öffentliche Inserat enthält die strukturierten Felder, die Wettbewerbsforschung, Marktgrößenbestimmung und Partnersuche antreiben: Unternehmensname, Bewertung, angebotene Dienstleistungen, Standort, Stundensatzband und ein Link zum vollständigen Profil. Für jeden, der eine Branche kartiert oder eine Anbieter-Shortlist erstellt, sind diese öffentlichen Verzeichnisdaten das Rohmaterial, und die manuelle Erfassung über Dutzende von Agenturen hinweg ist langsam und fehleranfällig.

Dieser Leitfaden zeigt Ihnen, wie Sie GoodFirms scrapen mit Python auf zuverlässige Weise. Sie bauen einen kleinen, lauffähigen Scraper, der gerenderte GoodFirms-Seiten über die Crawling API abruft, Unternehmensdatensätze aus einem Kategorieeintrag sammelt, die Felder mit BeautifulSoup parst, Paginierung verarbeitet und sauberes JSON und CSV exportiert. Die gesamte Anleitung bleibt auf öffentliche Unternehmenseintrags-Daten beschränkt, und der Abschnitt zur Rechtslage am Ende ist kein Boilerplate. Lesen Sie ihn, bevor Sie das auf ein echtes Datenvolumen ansetzen.

Was Sie bauen werden

Ein Python-Skript, das eine öffentliche GoodFirms-Kategorie-URL entgegennimmt, die paginierten Sucheinträge durchläuft, einen strukturierten Datensatz pro Unternehmen extrahiert und dann in einzelne Profile für die tieferen Felder einsteigt. Das laufende Beispiel sind Webentwicklungsagenturen in London. Wir ziehen folgende Felder:

  • Unternehmensname der aufgeführte Geschäftsname auf der Verzeichniskarte.
  • Bewertung der öffentliche Rezensionswert im Eintrag.
  • Dienstleistungen die Dienstleistungskategorie oder der Slogan, unter dem das Unternehmen aufgeführt ist.
  • Standort die auf der Karte angezeigte Stadt und das Land.
  • Stundensatz das Stundensatzband aus dem Unternehmensprofil.
  • Profil-URL der kanonische Link zur vollständigen Profilseite.

Warum eine einfache Anfrage bei GoodFirms scheitert

Fordern Sie eine GoodFirms-Kategorie- oder Profil-URL mit einem einfachen HTTP-Client an, erhalten Sie oft Status 200 mit nur einem Bruchteil der Eintragsdat im Body. Zwei Faktoren arbeiten gegen Sie. Erstens lädt GoodFirms einen Großteil seines Verzeichnisgitters und der Profildetails im Browser durch JavaScript, sodass das initiale HTML eine dünne Hülle ist, die sich erst füllt, nachdem die Skripte der Seite ausgeführt wurden. Unternehmenskarten aus dieser ersten Antwort herauszuziehen kann einen unvollständigen Satz ergeben oder spät gerenderte Felder verpassen. Zweitens beobachtet ein geschäftiges B2B-Verzeichnis automatisierten Traffic: Datacenter-IPs und Nicht-Browser-Anfragemuster werden ratenbegrenzt, blockiert oder herausgefordert, bevor sie den gerenderten Inhalt erreichen.

Ein funktionierender GoodFirms-Scraper benötigt also zwei Dinge in einer Anfrage: einen Browser, der die Seite rendert, und eine IP, die die Plattform als echten Besucher einliest. Sie können das selbst mit einem Headless-Browser plus rotierenden Residential Proxies zusammenbauen, aber das am Laufen zu halten ist der Großteil der Arbeit. Die Crawling API bündelt beides in einem einzigen Aufruf: Senden Sie ihr die URL mit einem JavaScript-Token, sie rendert die Seite hinter einer vertrauenswürdigen IP und gibt fertiges HTML zurück. Den Hintergrund dazu, warum clientseitiges Rendering naive Scraper bricht, behandelt der Leitfaden zum Crawlen von JavaScript-Websites ausführlich.

Warum das JS-Token

Crawlbase bietet zwei Token-Typen. Das normale Token ruft statisches HTML ab; das JavaScript-Token (JS-Token) rendert die Seite zuerst in einem echten Browser. Da GoodFirms Teile seines Verzeichnisses und seiner Profilseiten clientseitig füllt, ist das JS-Token hier der sichere Standard: Es gibt das fertige Markup zurück statt der dünnen Hülle, die ein einfacher Fetch liefern würde, sodass BeautifulSoup etwas Nützliches zum Parsen hat.

Voraussetzungen

Sie benötigen ein paar Dinge, bevor Sie mit dem Programmieren beginnen. Nichts davon dauert lange.

Grundlegende Python-Kenntnisse. Sie sollten in der Lage sein, ein Python-Skript zu schreiben und auszuführen sowie Pakete mit pip zu installieren. Wenn Sie neu beim Parsing sind, ist der BeautifulSoup-Leitfaden eine gute Ergänzung zu diesem Tutorial, und die umfassendere Anleitung zum Website-Scrapen mit Python deckt die Grundlagen ab.

Python 3.8 oder neuer. Überprüfen Sie Ihre Version mit python --version. Falls Sie es nicht haben, installieren Sie es von python.org oder über eine Distribution wie Anaconda, und stellen Sie sicher, dass Python in Ihrem PATH ist.

Ein Crawlbase-Konto und ein JS-Token. Registrieren Sie sich, öffnen Sie Ihr Dashboard und kopieren Sie Ihr JavaScript-Token (JS-Token) von der Kontodokumentationsseite. Crawlbase enthält bis zu 20.000 kostenlose Anfragen zum Starten, was für die Durcharbeitung dieses Leitfadens mehr als ausreichend ist. Behandeln Sie das Token wie ein Passwort: Es authentifiziert Ihre Anfragen, halten Sie es also aus der Versionskontrolle heraus.

Projekt einrichten

Erstellen Sie eine virtuelle Umgebung, damit Projektabhängigkeiten isoliert bleiben, dann installieren Sie die Bibliotheken, die der Scraper benötigt.

bash
python --version

python -m venv goodfirms_env
source goodfirms_env/bin/activate

pip install crawlbase beautifulsoup4

Aktivieren Sie die Umgebung unter Windows mit goodfirms_env\Scripts\activate statt der source-Zeile. Zwei Abhängigkeiten erledigen die Arbeit: crawlbase ist der offizielle Client für die Crawling API, und beautifulsoup4 parst das zurückgegebene HTML, sodass Sie einzelne Felder per CSS-Selektor extrahieren können. Sowohl json als auch csv werden mit der Standardbibliothek geliefert, sodass für den Exportschritt nichts weiter installiert werden muss.

Schritt 1: Eine gerenderte GoodFirms-Seite abrufen

Beginnen Sie damit, eine fertige Seite zu laden. Importieren Sie die CrawlingAPI-Klasse, initialisieren Sie sie mit Ihrem JS-Token und fordern Sie eine GoodFirms-Kategorie-URL an. Übergeben Sie ajax_wait und page_wait, damit die API auf den dynamischen Inhalt wartet, bevor die Seite erfasst wird. Den Crawlbase cb_status (legacy pc_status) zu prüfen, bevor Sie parsen, sorgt dafür, dass Fehler laut statt still auftreten.

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

OPTIONS = {
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/122.0",
    "ajax_wait": "true",
    "page_wait": 5000,
}

def crawl(page_url):
    response = api.get(page_url, OPTIONS)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

if __name__ == "__main__":
    listing_url = "https://www.goodfirms.co/companies/web-development-agency/london"
    html = crawl(listing_url)
    print(html[:500] if html else "No HTML returned")

Die beiden Wait-Optionen sind wichtig für ein clientseitig gerendertes Ziel. ajax_wait weist die API an, auf asynchrone Inhalte zu warten, und page_wait hält eine feste Anzahl von Millisekunden nach dem Laden an, damit spät gerenderte Karten vor der Erfassung erscheinen. Fünf Sekunden sind ein vernünftiger Ausgangspunkt; erhöhen Sie den Wert, wenn Ergebnisse mager zurückkommen. Führen Sie das Skript mit python goodfirms_scraper.py aus und Sie sollten echtes GoodFirms-Verzeichnis-Markup sehen, nicht die Hülle, die eine einfache Anfrage zurückgibt. Das bestätigt, dass das Rendering funktioniert, bevor Sie einen einzigen Selektor schreiben.

Crawlbase Crawling API

GoodFirms benötigt eine gerenderte Seite hinter einer vertrauenswürdigen IP, in einem einzigen Aufruf, was genau das ist, was die obigen ajax_wait- und page_wait-Optionen einrichten. Die Crawling API nimmt ein JS-Token, führt die Seite in einem echten Browser aus, rotiert serverseitig durch Residential IPs und gibt Ihnen fertiges HTML zurück, sodass Sie keinen eigenen Headless-Browser-Pool und keinen Proxy-Pool betreiben müssen. Testen Sie es zunächst auf einer öffentlichen Kategorieseite im kostenlosen Kontingent.

Schritt 2: Die Eintrags-Selektoren identifizieren

Bevor Sie den Parser schreiben, überprüfen Sie die Kategorieseite in den Entwicklertools Ihres Browsers (Rechtsklick und Untersuchen wählen oder Ctrl + Shift + I drücken), um die Elemente zu finden, die jedes Unternehmen umhüllen. Bei den GoodFirms-Sucheinträgen lebt jede Unternehmenskarte in einem Listenelement, und die Felder werden diesen Selektoren zugeordnet:

  • Unternehmensname befindet sich in einem <h3> mit der Klasse firm-name.
  • Standort ist ein <div> mit der Klasse firm-location.
  • Dienstleistungskategorie ist ein <div> verschachtelt unter firm-content mit der Klasse tagline.
  • Bewertung erscheint in einem <span> mit der Klasse rating-number.
  • Profil-URL ist der href eines <a> mit der Klasse visit-profile, innerhalb von firm-urls.

Mit diesen Selektoren laden Sie das gerenderte HTML in BeautifulSoup und ziehen jedes Feld pro Karte heraus. Jede Suche ist abgesichert, sodass ein fehlendes Feld einen Standardwert zurückgibt statt den Lauf zum Absturz zu bringen.

python
from bs4 import BeautifulSoup

def extract_company(card):
    name = card.select_one("h3.firm-name")
    location = card.select_one("div.firm-location")
    category = card.select_one("div.firm-content > div.tagline")
    rating = card.select_one("span.rating-number")
    link = card.select_one("div.firm-urls > a.visit-profile")

    return {
        "name": name.get_text(strip=True) if name else "",
        "location": location.get_text(strip=True) if location else "",
        "category": category.get_text(strip=True) if category else "",
        "rating": rating.get_text(strip=True) if rating else "No rating",
        "profile_url": link["href"] if link else "",
    }

def parse_listings(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("ul.firm-directory-list > li.firm-wrapper")
    return [extract_company(card) for card in cards]

Der Container-Selektor ul.firm-directory-list > li.firm-wrapper geht von der Verzeichnisliste bis zu jeder Unternehmenskarte, und extract_company liest die fünf Felder aus dem Inneren. Die Inline-Guards verhindern, dass eine Karte, der etwa eine Bewertung fehlt, die Schleife unterbricht: Sie fällt stattdessen auf "No rating" zurück.

Selektoren verändern sich

Verzeichniswebsites überarbeiten ihr Markup ohne Vorankündigung, und generierte Klassennamen können sich zwischen Besuchen ändern. Behandeln Sie die Selektoren hier als Ausgangspunkt, nicht als Vertrag. Wenn eine Liste leer zurückkommt, überprüfen Sie die Live-Seite in den Entwicklertools Ihres Browsers und aktualisieren Sie den Selektor. Regelmäßige Selektor-Wartung ist für jeden Produktions-Scraper normal, kein Zeichen dafür, dass etwas kaputt ist.

Schritt 3: Paginierung über Eintragsseiten verarbeiten

Eine Kategorieseite ist ein Ausschnitt des Ergebnissatzes. GoodFirms paginiert mit einem page-Query-Parameter, sodass Sie jede Seite durchlaufen und die Datensätze sammeln. Ein kleiner Retry-Wrapper um den Fetch verhindert, dass eine einzige langsame Seite den Lauf beendet.

python
import time

def fetch_html(page_url, max_retries=2):
    for attempt in range(max_retries + 1):
        html = crawl(page_url)
        if html:
            return html
        if attempt < max_retries:
            print(f"Retrying ({attempt + 1}/{max_retries})...")
            time.sleep(1)
    print(f"Unable to fetch {page_url}")
    return None

def scrape_all_pages(base_url, num_pages=5):
    all_companies = []
    for page in range(1, num_pages + 1):
        url = f"{base_url}?page={page}"
        print(f"Scraping page {page}...")
        html = fetch_html(url)
        if html:
            all_companies.extend(parse_listings(html))
        time.sleep(2)
    return all_companies

fetch_html versucht einen fehlgeschlagenen Abruf bis zu zweimal mit einer kurzen Pause und gibt das HTML bei Erfolg zurück und None, sobald es aufgibt. scrape_all_pages hängt den page-Parameter an, parst die Karten und begrenzt das Crawling auf die Obergrenze von num_pages, damit eine große Kategorie nicht ausufert. Das time.sleep(2) zwischen den Seiten verlangsamt den Lauf.

Schritt 4: Den Eintrags-Scraper zusammenstellen

Verbinden Sie nun die Teile in einem lauffähigen Skript: Durchlaufen Sie die Seiten, sammeln Sie die Unternehmensdatensätze und exportieren Sie sie in JSON und CSV.

python
import csv
import json
import time
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

OPTIONS = {
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/122.0",
    "ajax_wait": "true",
    "page_wait": 5000,
}

def crawl(page_url):
    response = api.get(page_url, OPTIONS)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

def fetch_html(page_url, max_retries=2):
    for attempt in range(max_retries + 1):
        html = crawl(page_url)
        if html:
            return html
        if attempt < max_retries:
            time.sleep(1)
    return None

def extract_company(card):
    name = card.select_one("h3.firm-name")
    location = card.select_one("div.firm-location")
    category = card.select_one("div.firm-content > div.tagline")
    rating = card.select_one("span.rating-number")
    link = card.select_one("div.firm-urls > a.visit-profile")

    return {
        "name": name.get_text(strip=True) if name else "",
        "location": location.get_text(strip=True) if location else "",
        "category": category.get_text(strip=True) if category else "",
        "rating": rating.get_text(strip=True) if rating else "No rating",
        "profile_url": link["href"] if link else "",
    }

def parse_listings(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("ul.firm-directory-list > li.firm-wrapper")
    return [extract_company(card) for card in cards]

def scrape_all_pages(base_url, num_pages=5):
    all_companies = []
    for page in range(1, num_pages + 1):
        url = f"{base_url}?page={page}"
        print(f"Scraping page {page}...")
        html = fetch_html(url)
        if html:
            all_companies.extend(parse_listings(html))
        time.sleep(2)
    return all_companies

def save_outputs(records):
    with open("goodfirms_companies.json", "w") as f:
        json.dump(records, f, indent=2)
    if not records:
        return
    with open("goodfirms_companies.csv", "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=records[0].keys())
        writer.writeheader()
        writer.writerows(records)

def main():
    base_url = "https://www.goodfirms.co/companies/web-development-agency/london"
    companies = scrape_all_pages(base_url, num_pages=3)
    save_outputs(companies)
    print(f"Saved {len(companies)} companies")

if __name__ == "__main__":
    main()

Das Skript durchläuft bis zu drei Kategorieseiten, parst jede in Datensätze und verlangsamt die Schleife mit einem Zwei-Sekunden-Schlaf. save_outputs schreibt sowohl JSON als auch CSV und verwendet die Schlüssel des ersten Datensatzes als Kopfzeile, sodass Sie die Daten in der Form haben, die Ihr nachgelagertes Tool benötigt. Passen Sie num_pages und die Kategorie-URL an Ihre Zielbranche und Stadt an.

Wie die Ausgabe aussieht

Führen Sie das vollständige Skript mit python goodfirms_scraper.py aus und erhalten Sie einen sauberen strukturierten Datensatz pro Unternehmen, bereit für Analysen, eine Datenbank oder eine Tabellenkalkulation.

json
[
  {
    "name": "Unified Infotech",
    "location": "London, United Kingdom",
    "category": "Driving Digital Transformation with Advanced Tech",
    "rating": "5.0",
    "profile_url": "https://www.goodfirms.co/company/unified-infotech"
  },
  {
    "name": "instinctools",
    "location": "London, United Kingdom",
    "category": "Building Custom Software Solutions",
    "rating": "4.9",
    "profile_url": "https://www.goodfirms.co/company/instinctools"
  }
]

Die entsprechende CSV trägt dieselben Spalten, eine Zeile pro Unternehmen, die direkt in pandas oder jede Tabellenkalkulation für die Filterung nach Bewertung, Standort oder Dienstleistungskategorie eingefügt werden kann.

Schritt 5: Unternehmensprofilseiten scrapen

Die Einträge geben Ihnen Breite; die Profilseiten geben Ihnen Tiefe. Jede Profil-URL führt zu einer Seite mit der vollständigen Beschreibung des Unternehmens, dem Stundensatzband, der Teamgröße, dem Gründungsjahr und den Dienstleistungen. Untersuchen Sie eine Profilseite auf die gleiche Weise, und die tieferen Felder werden diesen Selektoren zugeordnet:

  • Unternehmensname ist ein <h1> mit itemprop="name".
  • Beschreibung ist ein <div> mit der Klasse profile-summary-text.
  • Stundensatz ist ein <span> innerhalb von div.profile-pricing.
  • Mitarbeiteranzahl ist ein <span> innerhalb von div.profile-employees.
  • Gründungsjahr ist ein <span> innerhalb von div.profile-founded.
  • Dienstleistungen kommen aus dem data-name-Attribut jedes <button> in ul.services-chart-list.
python
import re
import json
import time
from bs4 import BeautifulSoup

def text_of(soup, selector, default="N/A"):
    el = soup.select_one(selector)
    return el.get_text(strip=True) if el else default

def extract_profile(html, url):
    soup = BeautifulSoup(html, "html.parser")
    summary = soup.select_one("div.profile-summary-text")
    description = re.sub(r"\s+", " ", summary.get_text(strip=True)) if summary else "N/A"
    services = [b["data-name"] for b in soup.select("ul.services-chart-list button[data-name]")]

    return {
        "name": text_of(soup, 'h1[itemprop="name"]'),
        "profile_url": url,
        "description": description,
        "hourly_rate": text_of(soup, "div.profile-pricing > span"),
        "no_of_employees": text_of(soup, "div.profile-employees > span"),
        "year_founded": text_of(soup, "div.profile-founded > span"),
        "services": services,
    }

def scrape_profiles(profile_urls):
    profiles = []
    for url in profile_urls:
        print(f"Scraping profile: {url}")
        html = fetch_html(url)
        if html:
            profiles.append(extract_profile(html, url))
        time.sleep(2)
    return profiles

if __name__ == "__main__":
    profile_urls = [
        "https://www.goodfirms.co/company/unified-infotech",
        "https://www.goodfirms.co/company/instinctools",
    ]
    data = scrape_profiles(profile_urls)
    with open("goodfirms_profiles.json", "w") as f:
        json.dump(data, f, indent=2)
    print(f"Saved {len(data)} profiles")

Dies verwendet den fetch_html-Wrapper aus dem Eintrags-Scraper, sodass Rendering, Retries und das JS-Token übertragen werden. Der re.sub(r"\s+", " ")-Aufruf komprimiert die Leerzeichen-Runs, die Unternehmensbeschreibungen tendenziell tragen, und die Dienstleistungsliste liest das data-name-Attribut von jedem Chart-Button. Ein typischer Profil-Datensatz sieht so aus:

json
{
  "name": "Unified Infotech",
  "profile_url": "https://www.goodfirms.co/company/unified-infotech",
  "description": "Unified Infotech is a digital transformation partner serving enterprises with custom web, mobile, and software solutions...",
  "hourly_rate": "$50 - $99/hr",
  "no_of_employees": "50 - 249",
  "year_founded": "2010",
  "services": [
    "Web Development",
    "Software Development",
    "Web Designing (UI/UX)",
    "Mobile App Development",
    "E-commerce Development"
  ]
}

Übergeben Sie die profile_url-Werte aus dem Eintrags-Schritt an scrape_profiles und Sie erhalten das Stundensatzband und die Dienstleistungen für jedes Unternehmen im gleichen Lauf, wodurch Breite und Tiefe in einem Datensatz vereint werden.

Im Maßstab nicht geblockt werden

Auch wenn das Rendering erledigt ist, beobachtet ein geschäftiges Verzeichnis scraperartigen Traffic. Ein paar Gewohnheiten halten einen längeren Lauf bei jedem kommerziellen Ziel gesund.

  • Anfragen verlangsamen. Einträge in einer engen Schleife zu bombardieren ist der schnellste Weg, gedrosselt oder herausgefordert zu werden. Die obigen Zwei-Sekunden-Schlafs sind der Boden, nicht die Decke; verbreitern Sie sie für größere Jobs und variieren Sie Ihre Ziele statt eine Kategorie mit voller Geschwindigkeit zu crawlen.
  • Auf Rotation setzen. Ein Pool von Residential IPs verteilt Anfragen auf viele echte Nutzeradressen, sodass keine einzelne eine Ratenbegrenzung auslöst. Die Crawling API übernimmt das für Sie; wenn Sie Ihren eigenen Stack betreiben, ist das der Teil, den Sie richtig hinbekommen müssen.
  • Statuscodes lesen. Ein Lauf, der beginnt, Nicht-200-cb_status-Werte zurückzugeben, signalisiert Ihnen, dass die aktuelle Rate oder IP-Stufe nicht mehr ausreicht. Behandeln Sie das als Signal zum Zurückrudern, nicht als Lärm, den Sie ignorieren können.

Für größere Crawls stellt der asynchrone Crawler Anfragen in eine Warteschlange und liefert Ergebnisse an einen Webhook, was für das Ausführen vieler Kategorieseiten ohne offene Verbindungen geeignet ist. Für das umfassendere Playbook lesen Sie, wie Sie Websites scrapen, ohne geblockt zu werden. Und wenn Sie die breitere B2B-Anbieter-Landschaft kartieren, überträgt sich der gleiche Ansatz auf das Scrapen von Clutch, Superpages und andere lokale Unternehmenseinträge.

Ob das Scrapen von GoodFirms erlaubt ist, hängt von seinen Nutzungsbedingungen, Ihrer Rechtsordnung und dem ab, was Sie mit den Daten machen. GoodFirms schränkt automatisierten Zugriff und Massensammlung in seinen Bedingungen ein, sodass Scraping dagegen verstoßen kann, unabhängig davon, wie sorgfältig Ihr Tooling ist. Kein Code hier ändert das; er lässt nur den technischen Teil funktionieren. Lesen Sie GoodFirms' Nutzungsbedingungen und die robots.txt, bevor Sie beginnen, respektieren Sie die dort erklärten Ratenlimits und Crawl-Direktiven und halten Sie Ihr Anfragevolumen niedrig genug, dass Sie deren Server nicht belasten.

Der Umfang ist genauso wichtig wie die Höflichkeit. Bleiben Sie bei öffentlichen Unternehmenseintrags-Daten: dem Unternehmensnamen, der Bewertung, der Dienstleistungskategorie, dem Standort, dem Stundensatzband und dem Profil-Link, den jeder Besucher ohne Konto sehen kann. Ein Unternehmensprofil kann auch Kontaktdaten für das Unternehmen oder genannte Personen aufführen, und in dem Moment, wo Sie irgendetwas sammeln oder speichern, das eine Person identifiziert, gilt das Datenschutzrecht. Unter der DSGVO benötigen Sie eine Rechtsgrundlage zur Verarbeitung personenbezogener Daten, Personen können die Löschung verlangen, und wenn Sie gesammelte Kontaktinformationen für Outreach verwenden, regeln Regime wie die DSGVO und das US CAN-SPAM Act das ebenfalls: Sie benötigen Einwilligung oder eine andere Rechtsgrundlage, genaue Absenderinformationen und eine funktionierende Opt-out-Möglichkeit. Personen und Unternehmen können verlangen, nicht kontaktiert zu werden, und Sie müssen das respektieren. Vermeiden Sie das Scrapen von Inhalten hinter einem Login, und verteilen Sie GoodFirms' eigene redaktionelle Inhalte oder Rezensionstext nicht en masse weiter, da dieser urheberrechtlich geschützt ist.

Dieser Leitfaden ist bewusst auf öffentliche Eintrags- und Profilseiten beschränkt, weil das die Linie ist, die die Arbeit vertretbar hält. Er deckt nichts hinter einem Konto, die Massenerfassung persönlicher Kontaktdaten oder Versuche, die Authentifizierung zu umgehen, ab. Nur öffentliche Unternehmensdaten. Wenn Ihr Projekt mehr als das benötigt, ist der richtige Weg ein genehmigter: GoodFirms veröffentlicht Daten über seine eigenen Kanäle und Partnervereinbarungen. Prüfen Sie, ob eine offizielle API oder ein lizenzierter Feed Ihren Anwendungsfall abdeckt. Das ist der richtige Weg für kommerzielle oder Massennutzung, kein clevererer Scraper.

Zusammenfassung

Wichtigste Erkenntnisse

  • GoodFirms rendert Teile seiner Seiten clientseitig. Eine einfache Anfrage kann eine dünne Hülle zurückgeben, daher rendern Sie die Seite mit dem JS-Token, bevor Sie sie parsen.
  • Sie benötigen Rendering und eine vertrauenswürdige IP gemeinsam. Die Crawling API mit einem JS-Token erledigt beides in einem Aufruf; ajax_wait und page_wait steuern, wie lange auf Inhalte gewartet wird.
  • In zwei Schichten arbeiten. Parsen Sie die Kategorieeinträge für Name, Bewertung, Dienstleistungen, Standort und Profil-Link, dann folgen Sie jeder Profil-URL für das Stundensatzband, die Teamgröße und die Dienstleistungsliste.
  • Paginieren und exportieren. Durchlaufen Sie den page-Query-Parameter bis zu einer Obergrenze, verlangsamen Sie den Lauf mit kurzen Schlafs und schreiben Sie die Datensätze in JSON und CSV.
  • Auf öffentlichen Unternehmensdaten bleiben. Respektieren Sie GoodFirms' AGB und robots.txt, behandeln Sie persönliche Kontaktdaten als reguliert unter der DSGVO und CAN-SPAM mit einer Rechtsgrundlage und einer funktionierenden Opt-out-Möglichkeit und berühren Sie niemals Logins oder urheberrechtlich geschützte redaktionelle Inhalte.

Häufig gestellte Fragen

Warum gibt eine einfache Anfrage nur einen Teil der GoodFirms-Daten zurück?

Weil GoodFirms Teile seines Verzeichnisgitters und der Profildetails clientseitig mit JavaScript lädt. Das initiale HTML kann eine Hülle sein, die sich erst füllt, nachdem die Skripte ausgeführt wurden, sodass eine rohe Anfrage Status 200 mit fehlenden Karten oder Profilfeldern zurückgeben kann. Rendern Sie die Seite zuerst, um den vollständigen Satz zu erhalten, was das JS-Token der Crawling API übernimmt.

Brauche ich das normale Token oder das JS-Token für GoodFirms?

Verwenden Sie das JS-Token. Das normale Token ruft statisches HTML ab, das die Teile von GoodFirms verpassen kann, die im Browser rendern. Das JS-Token führt die Seite zuerst in einem echten Browser aus, sodass die Unternehmenskarten und Profilfelder vorhanden sind, wenn BeautifulSoup sie parst.

Welche Daten kann ich von GoodFirms scrapen?

Öffentliche Unternehmenseintrags-Felder: der Unternehmensname, die Bewertung, die Dienstleistungskategorie oder der Slogan, der Standort, das Stundensatzband, die Teamgröße, das Gründungsjahr und der Profil-Link. Bleiben Sie bei Daten, die für jeden Besucher ohne Konto sichtbar sind, und behandeln Sie alle Kontaktdaten für genannte Personen als personenbezogene Daten, die außerhalb des öffentlichen Eintrags-Umfangs liegen, den dieser Leitfaden abdeckt.

Meine Selektoren geben leere Ergebnisse zurück. Was hat sich geändert?

Höchstwahrscheinlich GoodFirms' Markup. Klassennamen wie firm-name, firm-location und rating-number sowie die Profilcontainer wie profile-pricing und services-chart-list können sich ohne Vorankündigung ändern, sodass Selektoren, die letzten Monat funktionierten, jetzt brechen können. Überprüfen Sie eine Live-Seite in den Entwicklertools Ihres Browsers und aktualisieren Sie die Selektoren. Regelmäßige Selektor-Wartung ist für jeden Produktions-Scraper normal.

Wie verarbeite ich Paginierung über eine Kategorie hinweg?

GoodFirms hängt einen page-Query-Parameter an die Kategorie-URL an. Durchlaufen Sie die Seiten in einer Schleife, parsen Sie die Unternehmenskarten auf jeder, begrenzen Sie das Crawling auf eine num_pages-Obergrenze, damit eine große Kategorie nicht ausufert, und fügen Sie einen kurzen Schlaf zwischen den Seiten ein. Die obige scrape_all_pages-Funktion zeigt die vollständige Schleife.

Kann ich gescrapte GoodFirms-Daten für Outreach oder kommerziell nutzen?

Behandeln Sie das als rechtliche, nicht als technische Frage. Alle von Ihnen gesammelten Kontaktinformationen sind personenbezogene Daten, sodass Outreach von Regimen wie der DSGVO und dem US CAN-SPAM Act geregelt wird: Sie benötigen eine Rechtsgrundlage oder Einwilligung, genaue Absenderangaben und eine funktionierende Opt-out-Möglichkeit, und Personen können verlangen, nicht kontaktiert zu werden. GoodFirms' Nutzungsbedingungen schränken auch die Wiederverwendung seiner Inhalte ein. Überprüfen Sie die Bedingungen, prüfen Sie, ob eine offizielle API oder ein lizenzierter Feed Ihren Anwendungsfall abdeckt, und suchen Sie rechtliche Beratung, bevor Sie ein Produkt oder eine Outreach-Liste auf Basis der Daten aufbauen.

Jetzt loslegen

Crawlen Sie jede Website im großen Maßstab, ohne gegen die Infrastruktur zu kämpfen.

Crawlbase übernimmt Proxys, Fingerprints und CAPTCHAs, damit Ihr Team Datenpipelines ausliefert, statt Crawl-Infrastruktur zu pflegen. 1.000 Anfragen kostenlos, keine Karte erforderlich.

Self-Service · Kein Verkaufsgespräch erforderlich · Enterprise-Crawl-Volumen verfügbar