Incident: Cloudflare Block bei Scraping

Das Problem: Einer unserer kritischsten globalen Datenlieferanten aktivierte als Schutzmechanismus unangekündigt strenge Cloudflare-WAF-Regeln inklusive Bot-Management auf Enterprise-Level. Unsere vollautomatisierten Data-Extraction-Scripte ernteten innerhalb von Minuten hunderte HTTP 403-Errors. Die betroffene Datenquelle lieferte täglich Marktpreisdaten für ca. 12.000 Retail-Produkte – ein 6-stündiger Ausfall des Scrapers bedeutete eine direkte Lücke in unseren Preisindex-Algorithmen.

Warum IP-Rotation allein katastrophal scheitert

Der erste Ansatz war der Wechsel auf einen Residential Proxy Pool mit über 5 Millionen rotierenden Privatadressen (via Bright Data). Die Blockrate sank kurzzeitig auf 40 %, stabilisierte sich aber schnell wieder bei 85 %. Der Grund: Modernes Cloudflare Bot-Management Version 6 agiert mehrschichtig. Schicht 1 ist IP-Reputation (adressiert durch Residential Proxies). Schicht 2 ist TLS-Fingerprinting: Jede Browser-Version erzeugt beim TLS-Handshake eine einzigartige Kombination aus Cipher-Suites, Extensions und deren Reihenfolge – den sogenannten JA3-Hash. Node.js's natives HTTPS-Modul produziert einen JA3-Hash, der sofort als nicht-browserbasiert erkannt wird. Schicht 3 ist Browser-Fingerprinting über JavaScript: Cloudflare injiziert Challenge-Code, der dutzende Browser-Properties prüft – navigator.webdriver (muss undefined sein), navigator.plugins.length (muss > 0 sein), Canvas-Render-Fingerabdrücke, WebGL-Renderer-String, AudioContext-Fingerprint und die Verfügbarkeit spezifischer Browser-APIs wie window.chrome.

Verification Dashboard

Anatomie der Stealth-Engine: Was konkret gepatcht wird

Puppeteer im Headless-Modus verrät sich durch mindestens 11 identifizierbare Fingerabdrücke. Das Stealth-Plugin adressiert die kritischsten: (1) navigator.webdriver wird über das Chrome DevTools Protocol auf undefined überschrieben, bevor die erste JavaScript-Execution stattfindet. (2) Die plugins-Property wird mit einem Mock aus 3 realistischen Plugin-Objekten befüllt (Chrome PDF Viewer, Chrome NaCl, Widevine). (3) window.chrome wird als vollständiges Objekt inklusive runtime-API eingefügt. (4) Der WebGL-Renderer-String (RENDERER und VENDOR) wird auf einen realistischen Wert gesetzt – z.B. „ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0, D3D11)". (5) Der User-Agent wird auf einen real existierenden Chrome-Build gesetzt und alle abgeleiteten Properties werden konsistent dazu konfiguriert. (6) Request-Timing wird durch zufällige Delays zwischen 800 ms und 3.2 Sekunden humanisiert.

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');

puppeteer.use(StealthPlugin());
puppeteer.use(AdblockerPlugin({ blockTrackers: true }));

async function scrapeWithStealth(url, proxyUrl) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-blink-features=AutomationControlled',
      '--disable-features=IsolateOrigins,site-per-process',
      `--proxy-server=${proxyUrl}`,
      '--window-size=1920,1080',
      '--lang=de-DE,de'
    ]
  });

  const page = await browser.newPage();
  await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1 });

  await page.setExtraHTTPHeaders({
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
    'Accept-Encoding': 'gzip, deflate, br',
    'Sec-Ch-Ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
    'Sec-Ch-Ua-Mobile': '?0',
    'Sec-Ch-Ua-Platform': '"Windows"',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-User': '?1',
    'Upgrade-Insecure-Requests': '1'
  });

  // Patch WebGL fingerprint via CDP before any JS runs
  const client = await page.target().createCDPSession();
  await client.send('Page.addScriptToEvaluateOnNewDocument', {
    source: `
      const getParam = WebGLRenderingContext.prototype.getParameter;
      WebGLRenderingContext.prototype.getParameter = function(param) {
        if (param === 37445) return 'Intel Inc.';             // VENDOR
        if (param === 37446) return 'Intel Iris OpenGL Engine'; // RENDERER
        return getParam.call(this, param);
      };
    `
  });

  await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
  await page.waitForTimeout(800 + Math.random() * 2400);

  const data = await page.evaluate(() =>
    Array.from(document.querySelectorAll('.price-item')).map(el => ({
      sku: el.dataset.sku,
      price: el.querySelector('.price-value')?.textContent
    }))
  );

  await browser.close();
  return data;
}

Ergebnis und rechtliche Einordnung

Nach Deployment der Stealth-Engine stieg die Extraction-Erfolgsrate von unter 15 % auf 99 %. Die durchschnittliche Zeit pro Seite stieg durch die humanisierten Delays von 400 ms auf ca. 2.1 Sekunden – was paradoxerweise die Server-Last des Anbieters senkt, da wir nun keine Retry-Loops mehr feuern. Alle Scraping-Aktivitäten finden innerhalb der in den Terms-of-Service der jeweiligen Plattformen explizit tolerierten Grenzen für interne Marktforschungszwecke statt, und PII-Daten werden nicht extrahiert.