Wie man JavaScript-lastige Websites scrapt

Scrape JavaScript-erweiterte Inhalte mit kopflosen Browsern und Proxies. Puppeteer, Playwright und Chromedp Setup Guides mit Performance-Optimierung und API-Interception-Strategien.

Wie man JavaScript-lastige Websites scrapt

Die Herausforderung von JavaScript-Rendered Content

Moderne Webseiten verlassen sich zunehmend auf JavaScript, um Inhalte zu machen. Einseitige Anwendungen (SPAs) mit React, Vue oder Angular laden eine minimale HTML-Shell, dann holen und stellen Daten Client-Seite. Wenn Sie eine einfache HTTP-Anfrage an diese Seiten stellen, erhalten Sie eine leere oder unvollständige Seite, weil der Inhalt erst nach JavaScript-Ausführung existiert.

JavaScript-heavy-Websites zu blättern erfordert Kopflose Browser — reale Browser-Engines ohne sichtbares Fenster, das JavaScript ausführen kann, DOM rendern und mit Seitenelementen interagieren kann. Kombiniert mit Proxies, kopflose Browser entsperren Daten von sogar den dynamischsten Websites.

Dieser Führer ist Teil unseres Kompletter Leitfaden für Web Scraping Proxies. Zur Vermeidung der Erkennung bei Verwendung von kopflosen Browsern siehe Wie Anti-Bot-Systeme Proxies erkennen.

Wann benötigen Sie einen Headless Browser?

Wann benötigen Sie einen Headless Browser?
SzenarienEinfaches HTTPHeadless Browser
Statische HTML-SeitenFunktioniert perfektÜberqualifikation
Servergestützte Seiten mit APIWerke (hit die API direkt)Nicht nötig
SPA (React, Vue, Angular)Holt leere SchaleErforderlich
Infinite scroll / fazy loadingCannot TriggerErforderlich
Inhalt hinter Anmeldung + JSSchwerEmpfohlen
Seiten mit anti-bot JS-ChecksFehlererkennungErforderlich
Überprüfen Sie immer, ob die Website eine API oder serverseitige Rendering hat, bevor Sie für einen kopflosen Browser zu erreichen. Viele "JavaScript-heavy"-Seiten haben tatsächlich API-Endpunkte, die saubere JSON zurückgeben - viel schneller und billiger zu kratzen.

Puppenspieler + Proxies (Node.js)

Puppeteer steuert Chrome/Chromium programmatisch. Es ist das reifeste kopflose Browser-Tool für Node.js.

Basic Setup mit ProxyHat

const puppeteer = require('puppeteer');
async function scrapeWithPuppeteer(url) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      '--proxy-server=http://gate.proxyhat.com:8080',
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage',
    ],
  });
  const page = await browser.newPage();
  // Authenticate with proxy
  await page.authenticate({
    username: 'USERNAME',
    password: 'PASSWORD',
  });
  // Set realistic viewport and user agent
  await page.setViewport({ width: 1920, height: 1080 });
  await page.setUserAgent(
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
    '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
  );
  try {
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
    // Wait for specific content to render
    await page.waitForSelector('.product-list', { timeout: 10000 });
    const content = await page.content();
    const data = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('.product-item')).map(el => ({
        name: el.querySelector('.product-name')?.textContent?.trim(),
        price: el.querySelector('.product-price')?.textContent?.trim(),
        url: el.querySelector('a')?.href,
      }));
    });
    return { html: content, data };
  } finally {
    await browser.close();
  }
}
// Usage
const result = await scrapeWithPuppeteer('https://example.com/products');
console.log(`Found ${result.data.length} products`);

Optimierte Multi-Page-Schraping

const puppeteer = require('puppeteer');
class PuppeteerScraper {
  constructor(concurrency = 3) {
    this.concurrency = concurrency;
    this.browser = null;
  }
  async init() {
    this.browser = await puppeteer.launch({
      headless: 'new',
      args: [
        '--proxy-server=http://gate.proxyhat.com:8080',
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
        '--disable-extensions',
      ],
    });
  }
  async scrapePage(url) {
    const page = await this.browser.newPage();
    await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
    await page.setViewport({ width: 1920, height: 1080 });
    // Block unnecessary resources to speed up loading
    await page.setRequestInterception(true);
    page.on('request', (req) => {
      const type = req.resourceType();
      if (['image', 'stylesheet', 'font', 'media'].includes(type)) {
        req.abort();
      } else {
        req.continue();
      }
    });
    try {
      await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
      const content = await page.content();
      return { url, status: 'success', html: content };
    } catch (err) {
      return { url, status: 'error', error: err.message };
    } finally {
      await page.close();
    }
  }
  async scrapeMany(urls) {
    const results = [];
    for (let i = 0; i < urls.length; i += this.concurrency) {
      const batch = urls.slice(i, i + this.concurrency);
      const batchResults = await Promise.all(
        batch.map(url => this.scrapePage(url))
      );
      results.push(...batchResults);
      console.log(`Progress: ${results.length}/${urls.length}`);
    }
    return results;
  }
  async close() {
    if (this.browser) await this.browser.close();
  }
}
// Usage
const scraper = new PuppeteerScraper(3);
await scraper.init();
const results = await scraper.scrapeMany(urls);
await scraper.close();

Playwright + Proxies (Python)

Playwright ist eine neuere Alternative, die Chromium, Firefox und WebKit unterstützt. Seine Python API ist sauber und gut für das Abkratzen geeignet.

Basic Setup

from playwright.sync_api import sync_playwright
def scrape_with_playwright(url: str) -> dict:
    """Scrape a JavaScript-heavy page using Playwright with ProxyHat proxy."""
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True,
            proxy={
                "server": "http://gate.proxyhat.com:8080",
                "username": "USERNAME",
                "password": "PASSWORD",
            }
        )
        context = browser.new_context(
            viewport={"width": 1920, "height": 1080},
            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",
        )
        page = context.new_page()
        try:
            page.goto(url, wait_until="networkidle", timeout=60000)
            # Wait for dynamic content
            page.wait_for_selector(".product-list", timeout=10000)
            # Extract data using page.evaluate
            products = page.evaluate("""() => {
                return Array.from(document.querySelectorAll('.product-item')).map(el => ({
                    name: el.querySelector('.product-name')?.textContent?.trim(),
                    price: el.querySelector('.product-price')?.textContent?.trim(),
                    url: el.querySelector('a')?.href,
                }));
            }""")
            return {"url": url, "products": products, "html": page.content()}
        finally:
            browser.close()

Async Playwright für Parallel Scrap

import asyncio
from playwright.async_api import async_playwright
async def scrape_batch(urls: list[str], concurrency: int = 3) -> list[dict]:
    """Scrape multiple JS-heavy pages in parallel using Playwright."""
    results = []
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            proxy={
                "server": "http://gate.proxyhat.com:8080",
                "username": "USERNAME",
                "password": "PASSWORD",
            }
        )
        semaphore = asyncio.Semaphore(concurrency)
        async def scrape_one(url: str) -> dict:
            async with semaphore:
                context = await browser.new_context(
                    viewport={"width": 1920, "height": 1080},
                )
                page = await context.new_page()
                # Block heavy resources
                await page.route("**/*.{png,jpg,jpeg,gif,svg,css,woff,woff2}",
                                 lambda route: route.abort())
                try:
                    await page.goto(url, wait_until="networkidle", timeout=30000)
                    html = await page.content()
                    return {"url": url, "status": "success", "html": html}
                except Exception as e:
                    return {"url": url, "status": "error", "error": str(e)}
                finally:
                    await context.close()
        tasks = [scrape_one(url) for url in urls]
        results = await asyncio.gather(*tasks)
        await browser.close()
    return results
# Usage
urls = [f"https://example.com/product/{i}" for i in range(50)]
results = asyncio.run(scrape_batch(urls, concurrency=5))

Gehen Sie: Verchromt mit Proxies

package main
import (
    "context"
    "fmt"
    "log"
    "time"
    "github.com/chromedp/chromedp"
)
func scrapeJSPage(targetURL string) (string, error) {
    // Configure proxy
    opts := append(chromedp.DefaultExecAllocatorOptions[:],
        chromedp.ProxyServer("http://gate.proxyhat.com:8080"),
        chromedp.Flag("headless", true),
        chromedp.Flag("disable-gpu", true),
        chromedp.Flag("no-sandbox", true),
        chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "+
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"),
    )
    allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
    defer cancel()
    ctx, cancel := chromedp.NewContext(allocCtx)
    defer cancel()
    ctx, cancel = context.WithTimeout(ctx, 60*time.Second)
    defer cancel()
    var htmlContent string
    err := chromedp.Run(ctx,
        chromedp.Navigate(targetURL),
        chromedp.WaitVisible(".product-list", chromedp.ByQuery),
        chromedp.OuterHTML("html", &htmlContent),
    )
    if err != nil {
        return "", fmt.Errorf("scrape failed: %w", err)
    }
    return htmlContent, nil
}
func main() {
    html, err := scrapeJSPage("https://example.com/products")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Got %d bytes of rendered HTML\n", len(html))
}

Leistungsoptimierungsstrategien

Headless Browser sind 10-50x langsamer als einfache HTTP-Anfragen. Hier sind Strategien, um die Leistungslücke zu minimieren:

1. Block unnötige Ressourcen

Bilder, CSS, Schriften und Mediendateien werden für die Datenextraktion nicht benötigt. Blockieren sie drastisch beschleunigt Seitenlasten:

# Playwright resource blocking
async def fast_scrape(page, url):
    # Block images, CSS, fonts, media
    await page.route("**/*.{png,jpg,jpeg,gif,svg,css,woff,woff2,mp4,webm}",
                     lambda route: route.abort())
    # Also block tracking scripts
    await page.route("**/*google-analytics*", lambda route: route.abort())
    await page.route("**/*facebook*", lambda route: route.abort())
    await page.goto(url, wait_until="domcontentloaded")  # Faster than networkidle
    return await page.content()

2. Verwenden Sie die richtige Wartestrategie

2. Verwenden Sie die richtige Wartestrategie
StrategieGeschwindigkeitZuverlässigkeitAnwendungsfall
domcontentloadedSchnellKann async Daten vermissenSeiten mit Inline-Daten
loadMittelGut.Die meisten Seiten
networkidleLangsamHöchstSchwere SPAs, unendliche Scroll
Spezifische AuswahlVariabelHöchstWenn Sie das Zielelement kennen

3. Browser-Instanzen wiederverwenden

Das Starten eines Browsers dauert 1-3 Sekunden. Für den Stapelabbau starten Sie einmal und erstellen Sie neue Seiten/Kontexte für jede URL:

from playwright.sync_api import sync_playwright
class BrowserPool:
    """Reusable browser pool for efficient headless scraping."""
    def __init__(self, pool_size: int = 3):
        self.pool_size = pool_size
        self.playwright = None
        self.browsers = []
    def start(self):
        self.playwright = sync_playwright().start()
        for _ in range(self.pool_size):
            browser = self.playwright.chromium.launch(
                headless=True,
                proxy={
                    "server": "http://gate.proxyhat.com:8080",
                    "username": "USERNAME",
                    "password": "PASSWORD",
                }
            )
            self.browsers.append(browser)
    def get_browser(self, index: int):
        return self.browsers[index % self.pool_size]
    def stop(self):
        for browser in self.browsers:
            browser.close()
        self.playwright.stop()
# Usage
pool = BrowserPool(pool_size=3)
pool.start()
for i, url in enumerate(urls):
    browser = pool.get_browser(i)
    context = browser.new_context()
    page = context.new_page()
    page.goto(url, wait_until="networkidle")
    html = page.content()
    context.close()
pool.stop()

4. Intercept API Anrufe anstelle von Parsing DOM

Viele SPAs holen Daten von APIs. Beginnen Sie diese API Anrufe direkt – Sie erhalten sauber JSON ohne Parsing HTML:

const puppeteer = require('puppeteer');
async function interceptAPIData(url) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: ['--proxy-server=http://gate.proxyhat.com:8080'],
  });
  const page = await browser.newPage();
  await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
  const apiResponses = [];
  // Intercept XHR/fetch responses
  page.on('response', async (response) => {
    const url = response.url();
    if (url.includes('/api/') || url.includes('/graphql')) {
      try {
        const json = await response.json();
        apiResponses.push({ url, data: json });
      } catch {
        // Not JSON, skip
      }
    }
  });
  await page.goto(url, { waitUntil: 'networkidle2' });
  await browser.close();
  return apiResponses;
}
// Get clean API data instead of scraping DOM
const data = await interceptAPIData('https://example.com/products');
console.log(`Intercepted ${data.length} API calls`);

Headless Browser vs HTTP Vergleich

Headless Browser vs HTTP Vergleich
MetricEinfaches HTTP + ProxyHeadless Browser + Proxy
Geschwindigkeit pro Seite0,5-2 Sekunden3-15 Sekunden
Speicher pro Instanz~50 MB200-500 MB
CPU-NutzungMinimalBedeutung
Bandbreite pro Seite50-200 KB2-10 MB (mit Ressourcen)
JavaScript-RenderingNeinVollständig
Anti-Bot-BypassUnternehmenBesser (realer Browser)
Laufende Seiten100+3-10 pro Maschine

Best Practices

  • Versuchen Sie immer zuerst HTTP. Überprüfen Sie API-Endpunkte, Server-rendered Content oder JSON eingebettet in das HTML, bevor Sie einen kopflosen Browser verwenden.
  • Blockieren Sie unnötige Ressourcen. Bilder, CSS und Schriften ergänzen die Ladezeit ohne Datenbereitstellung.
  • Benutzen Sie spezielle Selektoren zum Warten. networkidle ist sicher, aber langsam. Warten Sie auf das gewünschte Element.
  • Browser-Instanzen wiederverwenden. Starten Sie einmal, erstellen Sie neue Kontexte pro Seite.
  • Intercept API Anrufe. Viele SPAs laden Daten über APIs – abfangen den JSON direkt.
  • Begrenzung der Konkurrenz. Kopflose Browser sind speicherintensiv. 3-5 gleichzeitige Seiten pro GB RAM ist eine gute Regel.
  • Benutzen Sie Wohn-Proxies. ProxyHat Wohnwagen bieten die höchsten Treuhand-Scores, die Erkennung reduzieren, wenn Headless-Browser laufen.

Für die Handhabung von CAPTCHAs, dass kopflose Browser auftreten, siehe Umgang mit CAPTCHAs Wenn Sie scrapen. Zum Skalieren kopfloser Browser-Schrott, lesen Wie man die Scraping Infrastruktur skaliert.

Beginnen Sie mit dem Python SDK, Node SDK, oder SDK zur Proxy-Integration und Erkundung ProxyHat für Web Scraping.

Häufig gestellte Fragen

Brauche ich immer einen kopflosen Browser für JavaScript-Seiten?

Nein. Viele JavaScript-heavy-Seiten laden Daten von API-Endpunkten. Überprüfen Sie die Netzwerk-Tab des Browsers für XHR/Fetch-Anfragen – wenn die Daten von einer API stammen, können Sie diese API direkt mit einfachen HTTP-Anfragen über einen Proxy anrufen, der viel schneller ist.

Puppenspieler oder Playwright – was ist besser für das Abkratzen?

Playwright wird im Allgemeinen für neue Projekte empfohlen. Es unterstützt mehrere Browser-Engines (Chromium, Firefox, WebKit), hat bessere Auto-Wartung, native Async-Unterstützung in Python, und integrierte Proxy-Konfiguration. Puppeteer ist reifer und hat ein größeres Ökosystem, wenn Sie in der Node.js-Welt sind.

Wie viele kopflose Browser-Seiten kann ich gleichzeitig ausführen?

Jede Seite verbraucht 200-500 MB RAM. Auf einer Maschine mit 8 GB RAM sind 3-10 gleichzeitige Seiten realistisch. Verwenden Sie Ressourcensperrung (Bilder, CSS) um Speicher zu reduzieren. Für höhere Konkurrenz, verteilen Sie auf mehrere Maschinen mit einer queuebasierten Architektur.

Warum Proxies mit kopflosen Browsern verwenden?

Auch mit einem echten Browser werden wiederholte Anfragen aus demselben IP blockiert. Proxies drehen Ihre IP, so dass jede Seitenlast von einem anderen Benutzer zu kommen scheint. Residential Proxies durch ProxyHat bieten die höchsten Treuhandpunkte, Minimierung Blöcke und CAPTCHAs.

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