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?
| Szenarien | Einfaches HTTP | Headless Browser |
|---|---|---|
| Statische HTML-Seiten | Funktioniert perfekt | Überqualifikation |
| Servergestützte Seiten mit API | Werke (hit die API direkt) | Nicht nötig |
| SPA (React, Vue, Angular) | Holt leere Schale | Erforderlich |
| Infinite scroll / fazy loading | Cannot Trigger | Erforderlich |
| Inhalt hinter Anmeldung + JS | Schwer | Empfohlen |
| Seiten mit anti-bot JS-Checks | Fehlererkennung | Erforderlich |
Ü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
| Strategie | Geschwindigkeit | Zuverlässigkeit | Anwendungsfall |
|---|---|---|---|
domcontentloaded | Schnell | Kann async Daten vermissen | Seiten mit Inline-Daten |
load | Mittel | Gut. | Die meisten Seiten |
networkidle | Langsam | Höchst | Schwere SPAs, unendliche Scroll |
| Spezifische Auswahl | Variabel | Höchst | Wenn 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
| Metric | Einfaches HTTP + Proxy | Headless Browser + Proxy |
|---|---|---|
| Geschwindigkeit pro Seite | 0,5-2 Sekunden | 3-15 Sekunden |
| Speicher pro Instanz | ~50 MB | 200-500 MB |
| CPU-Nutzung | Minimal | Bedeutung |
| Bandbreite pro Seite | 50-200 KB | 2-10 MB (mit Ressourcen) |
| JavaScript-Rendering | Nein | Vollständig |
| Anti-Bot-Bypass | Unternehmen | Besser (realer Browser) |
| Laufende Seiten | 100+ | 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.
networkidleist 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.






