Öffentliche LinkedIn-Daten scrapen: Rechtliche Grenzen, Proxies und Best Practices

Ein praktischer Leitfaden für Entwickler zum Scraping öffentlicher LinkedIn-Profile und Stellenausschreibungen mit Residential Proxies — mit Fokus auf rechtliche und ethische Grenzen.

Öffentliche LinkedIn-Daten scrapen: Rechtliche Grenzen, Proxies und Best Practices

Rechtlicher Rahmen: Was Sie wissen müssen, bevor Sie beginnen

Bevor wir uns den technischen Aspekten widmen, ist ein wichtiger Hinweis notwendig: Dieser Artikel behandelt ausschließlich den Zugriff auf öffentlich zugängliche Daten auf LinkedIn. Wir geben keine Rechtsberatung — konsultieren Sie unbedingt einen qualifizierten Juristen, bevor Sie ein Scraping-Projekt starten.

Der Fall hiQ Labs v. LinkedIn hat wichtige Präzedenzen geschaffen. hiQ Labs, ein Datenanalyse-Unternehmen, scrapte öffentliche LinkedIn-Profile, um KI-gestützte Talent-Analysen anzubieten. LinkedIn untersagte dies mit einer Abmahnung und technischen Blockaden. hiQ verklagte LinkedIn und argumentierte, dass öffentlich zugängliche Daten nicht unter den Computer Fraud and Abuse Act (CFAA) fallen.

Kernfrage: Darf ein Unternehmen den Zugriff auf öffentliche Daten auf seiner Website einschränken, oder verstoßen solche Einschränkungen gegen den Wettbewerb und die Informationsfreiheit?

2019 entschied ein Bundesgericht, dass hiQ wahrscheinlich Erfolg haben würde, da die CFAA nicht auf öffentlich zugängliche Daten anwendbar sei. LinkedIn legte Berufung ein. 2022 bestätigte ein Berufungsgericht weitgehend die Entscheidung der Vorinstanz. Der Supreme Court hat sich bisher nicht abschließend geäußert.

In der EU gelten zusätzlich die DSGVO und nationale Datenschutzgesetze. Selbst öffentlich zugängliche personenbezogene Daten dürfen nicht ohne Rechtsgrundlage verarbeitet werden. Ein berechtigtes Interesse muss sorgfältig abgewogen werden.

Was ist ohne Login öffentlich zugänglich?

LinkedIn unterscheidet strikt zwischen öffentlichen und nicht-öffentlichen Inhalten. Ohne Anmeldung sind folgende Daten typischerweise zugänglich:

  • Öffentliche Profil-URLs: Ein eingeschränkter Ausschnitt eines Profils — Name, aktuelle Position, Unternehmen, manchmal Ausbildung und Standort.
  • Öffentliche Unternehmensseiten: Basisinformationen zu Unternehmen, Mitarbeiterzahlen, Branche, Website.
  • Öffentliche Stellenausschreibungen: Titel, Beschreibung, Standort, Unternehmen — oft vollständig einsehbar.

Was nicht öffentlich ist ohne Login:

  • Vollständige Profil-Historien und Empfehlungen
  • Kontaktdaten (außer explizit veröffentlicht)
  • Private Aktivitäten, Nachrichten, Verbindungen
  • Sales Navigator und Recruiter-Daten

Warum Residential Proxies unverzichtbar sind

LinkedIn betreibt eines der aggressivsten Anti-Scraping-Systeme im Web. Die Gründe sind nachvollziehbar: Datenschutz der Nutzer, Schutz vor Konkurrenzanalyse, und die Verhinderung von Datenmissbrauch.

Warum Datacenter-Proxies scheitern:

  • LinkedIn pflegt umfangreiche IP-Reputation-Datenbanken
  • Datacenter-IP-Blöcke werden oft präventiv blockiert
  • Die Erkennungsrate für DC-Proxies liegt bei über 95%
  • Selbst rotierende DC-Proxies werden schnell erkannt

Residential Proxies nutzen IP-Adressen echter Privathaushalte. Sie erscheinen für LinkedIn wie normale Nutzer, die über typische Heimanschlüsse zugreifen. Das bedeutet jedoch nicht, dass Sie unbegrenzt scrapen können — Sie müssen sich wie ein normaler Nutzer verhalten.

Proxy-TypErkennungsrisikoEignung für LinkedInKosten
DatacenterSehr hoch (95%+)Nicht empfohlenNiedrig
ResidentialNiedrig (bei korrekter Nutzung)EmpfohlenMittel-Hoch
Mobile/4GSehr niedrigOptimal, aber teuerHoch
Kein ProxyIP wird schnell blockiertNur für kleine TestsKeine

Python + Playwright: Ein robuster Ansatz

Playwright ist ideal für LinkedIn-Scraping, da es echte Browser automatisiert und JavaScript rendert. Hier ist ein durchdachtes Beispiel mit Residential Proxies und realistischem Browser-Verhalten:

import asyncio
import random
from playwright.async_api import async_playwright

# ProxyHat Residential Proxy Konfiguration
PROXY_CONFIG = {
    "server": "http://gate.proxyhat.com:8080",
    "username": "user-country-US",  # US-IP für LinkedIn.com
    "password": "YOUR_PASSWORD"
}

# Realistische User-Agent Rotation
USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0"
]

async def scrape_profile(browser, profile_url: str) -> dict:
    """Scraped ein öffentliches LinkedIn-Profil."""
    context = await browser.new_context(
        user_agent=random.choice(USER_AGENTS),
        viewport={"width": 1920, "height": 1080},
        locale="en-US",
        timezone_id="America/New_York",
        proxy=PROXY_CONFIG
    )
    
    page = await context.new_page()
    
    try:
        # Realistische Verzögerung
        await asyncio.sleep(random.uniform(2, 5))
        
        await page.goto(profile_url, wait_until="networkidle", timeout=30000)
        
        # Prüfen auf Blockierung
        if "authwall" in page.url or "checkpoint" in page.url:
            print(f"Blockiert: {profile_url}")
            return None
        
        # Warten auf Inhaltsladung
        await page.wait_for_selector(".text-heading-xlarge", timeout=10000)
        
        # Daten extrahieren
        data = await page.evaluate("""() => {
            const name = document.querySelector('.text-heading-xlarge')?.innerText || '';
            const headline = document.querySelector('.text-body-medium')?.innerText || '';
            const location = document.querySelector('.text-body-small.inline')?.innerText || '';
            const company = document.querySelector('[aria-label="Current company"]')?.innerText || '';
            
            return { name, headline, location, company };
        }""")
        
        return data
        
    except Exception as e:
        print(f"Fehler bei {profile_url}: {e}")
        return None
    finally:
        await context.close()

async def main():
    profiles = [
        "https://www.linkedin.com/in/example-profile-1/",
        "https://www.linkedin.com/in/example-profile-2/"
    ]
    
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        
        results = []
        for url in profiles:
            data = await scrape_profile(browser, url)
            if data:
                results.append(data)
            # Wichtig: Pause zwischen Requests
            await asyncio.sleep(random.uniform(8, 15))
        
        await browser.close()
        return results

if __name__ == "__main__":
    results = asyncio.run(main())
    print(results)

Wichtige Aspekte dieses Codes:

  • Browser-Fingerprinting: Vollständiger Browser-Kontext mit realistischen Eigenschaften
  • Rate Limiting: 8-15 Sekunden Pause zwischen Requests — nicht unterschreiten
  • Error Handling: Erkennung von Authwalls und Checkpoints
  • Proxy-Rotation: Ändern Sie den Username für jede Session

Stellenausschreibungen scrapen: Spezifika

LinkedIn-Jobs sind oft vollständig öffentlich und weniger stark geschützt. Dennoch gelten strenge Rate-Limits.

import requests
from urllib.parse import quote
import time
import random

BASE_URL = "https://www.linkedin.com/jobs/search/"

def search_jobs(keywords: str, location: str, page: int = 0) -> dict:
    """Durchsucht LinkedIn-Stellenausschreibungen."""
    
    params = {
        "keywords": keywords,
        "location": location,
        "start": page * 25  # 25 Ergebnisse pro Seite
    }
    
    # ProxyHat Residential Proxy
    proxies = {
        "http": "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080",
        "https": "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
    }
    
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Accept-Language": "en-US,en;q=0.9",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
    }
    
    try:
        response = requests.get(
            BASE_URL,
            params=params,
            proxies=proxies,
            headers=headers,
            timeout=30
        )
        
        if response.status_code == 429:
            print("Rate limit erreicht. Warte 60 Sekunden...")
            time.sleep(60)
            return search_jobs(keywords, location, page)
        
        # Parse Job-Listings (vereinfacht)
        # In der Praxis: BeautifulSoup oder Playwright für JS-Rendering
        return {"status": response.status_code, "html": response.text[:500]}
        
    except Exception as e:
        print(f"Fehler: {e}")
        return None

def scrape_jobs_paginated(keywords: str, location: str, max_pages: int = 5):
    """Scraped mehrere Seiten mit angemessenem Rate Limiting."""
    results = []
    
    for page in range(max_pages):
        print(f"Scrape Seite {page + 1}...")
        data = search_jobs(keywords, location, page)
        if data:
            results.append(data)
        
        # Kritisch: Ausreichend warten
        delay = random.uniform(10, 20)
        time.sleep(delay)
    
    return results

# Beispiel-Aufruf
if __name__ == "__main__":
    jobs = scrape_jobs_paginated("Software Engineer", "Berlin", max_pages=3)
    print(f"{len(jobs)} Seiten gescraped")

Pagination und Filter

LinkedIn nutzt Offset-basierte Pagination mit 25 Ergebnissen pro Seite. Wichtige Filter-Parameter:

  • f_JT: Job-Typ (F, C, P, T, I, V für Full-time, Contract, etc.)
  • f_E: Erfahrungsniveau (1, 2, 3 für Entry, Associate, Senior)
  • f_WT: Arbeitsmodell (1=On-site, 2=Remote, 3=Hybrid)
  • distance: Umkreis in km

Wann Sie NICHT scrapen sollten

Ethische und rechtliche Grenzen sind nicht verhandelbar. Hier sind klare „Red Lines“:

1. Private Daten

Niemals auf Daten zugreifen, die nur für eingeloggte Nutzer sichtbar sind:

  • Private Nachrichten und InMail
  • Eingeschränkte Profil-Details
  • Verbindungen und Netzwerk-Informationen
  • Empfehlungen, die als privat markiert sind

2. Login-geschützte Bereiche

Das Scraping nach einem Login (auch mit eigenen Credentials) ist besonders riskant:

  • Verletzung der LinkedIn-Nutzungsbedingungen
  • Potentielle CFAA-Haftung in den USA
  • Konto-Sperrung und rechtliche Schritte

3. Sales Navigator und Recruiter

Diese Premium-Produkte sind explizit geschützt:

  • Erweiterte Suchfilter und -ergebnisse
  • InMail-Credits und Lead-Vorschläge
  • Angepasste Listen und gespeicherte Suchen

Grundsatz: Wenn Sie sich einloggen müssen, um auf Daten zuzugreifen, sind diese nicht „öffentlich“ im Sinne der hiQ-Entscheidung. Überschreiten Sie diese Grenze nicht.

Offizielle Alternativen: LinkedIn APIs

Für viele Anwendungsfälle sind offizielle APIs die bessere Wahl — legal sicher, zuverlässig, und mit Support.

APIZielgruppeDatenKosten
LinkedIn Marketing APIWerbetreibendeAnzeigen, Analytics, Lead GenKostenlos (mit Werbekonto)
LinkedIn Recruiter System ConnectRecruiting-ToolsKandidaten-Suche, InMailEnterprise-Preise
LinkedIn Learning APILMS-IntegrationenKursdaten, FortschrittEnterprise
Profile API (begrenzt)PartnerEigene Profil-DatenPartnership nötig

Das LinkedIn Partner Program ist der offizielle Weg für Datenzugriff. Die Hürden sind hoch, aber für ernsthafte Unternehmen oft machbar.

Ethische Best Practices

Respektieren Sie robots.txt

LinkedIn's robots.txt erlaubt bestimmte Pfade nicht. Auch wenn sie rechtlich nicht bindend ist, signalisiert sie die Präferenzen der Plattform:

# Beispiel: Prüfen vor dem Scraping
import urllib.robotparser

rp = urllib.robotparser.RobotFileParser()
rp.set_url("https://www.linkedin.com/robots.txt")
rp.read()

can_fetch = rp.can_fetch("*", "https://www.linkedin.com/jobs/search/")
print(f"Darf scrapen: {can_fetch}")  # Prüfen Sie das Ergebnis

Rate Limiting ist Pflicht

Aggressives Scraping schadet allen:

  • Maximal 60-100 Requests pro Stunde und IP
  • Vermeiden Sie Peak-Zeiten (US-Business-Stunden)
  • Implementieren Sie exponentielles Backoff bei Fehlern
  • Verteilen Sie Last über mehrere IPs und Zeiträume

Datenminimierung nach DSGVO

In der EU gilt das Prinzip der Datenminimierung:

  • Nur sammeln, was Sie wirklich brauchen
  • Personenbezogene Daten anonymisieren wo möglich
  • Speicherfristen definieren und einhalten
  • Datenschutz-Folgenabschätzung durchführen

Key Takeaways

  • Rechtlicher Rahmen: Der hiQ-Fall zeigt, dass öffentliche Daten unter CFAA möglicherweise zugänglich sind — aber die Rechtslage bleibt unsicher, besonders außerhalb der USA.
  • Nur öffentliche Daten: Beschränken Sie sich strikt auf Inhalte, die ohne Login sichtbar sind.
  • Residential Proxies sind Pflicht: LinkedIn erkennt Datacenter-IPs fast immer. Nutzen Sie echte Residential IPs mit geografischer Verteilung.
  • Rate Limiting schützt Sie: Verhalten Sie sich wie ein normaler Nutzer. Weniger ist mehr.
  • Offizielle APIs bevorzugen: Wenn verfügbar und bezahlbar, sind Partner-APIs der sicherere Weg.
  • Ethische Verantwortung: Denken Sie an den Datenschutz echter Personen hinter den Profilen.

Nächste Schritte

Für Ihre ersten Scraping-Experimente mit Residential Proxies besuchen Sie unseren ProxyHat Tarif-Vergleich. Unsere IPs aus über 195 Ländern eignen sich ideal für vorsichtige, verteilte Datenextraktion.

Weitere technische Details finden Sie in unserem Web Scraping Use Case Guide und im Artikel zu Proxy-Rotation.

Bereit loszulegen?

Zugang zu über 50 Mio. Residential-IPs in über 148 Ländern mit KI-gesteuerter Filterung.

Preise ansehenResidential Proxies
← Zurück zum Blog