- HTML 36.5%
- Rust 32.4%
- TypeScript 31.1%
Problem: PseudoClass::Host/HostContext/Part/Slotted/State waren im Enum aber parse_pseudo hatte KEINE match-Arms dafuer. ::part(button) und :host fuehrten zu parse failure (return None -> leeres Array — kein Crash, aber falsch). Fix: - parse_pseudo erkennt jetzt :: Prefix (pseudo-element notation) - Alle 5 Shadow DOM Pseudo-Classes parsen korrekt - ::before und ::after als Part(before/after) erkannt - Matching gibt immer false (Chrome-korrekt fuer non-shadow DOM) 79/79 Tests, kein Crash bei egal welchem CSS Selector. |
||
|---|---|---|
| crates | ||
| js-runtime | ||
| test-data | ||
| .gitignore | ||
| AGENDA.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| DOM-API-CHECKLIST.md | ||
| README.md | ||
Rust Headless Browser
Der neue De-facto-Standard für Web Scraping.
Ein non-visueller, spec-konformer Browser — voller JS-Stack, echtes DOM,
40-50× effizienter als Headless Chrome, bei gleicher Anti-Bot-Resistenz.
Mission
Einen Browser bauen, der kein Pixel rendert aber jede Website lädt —
schneller, günstiger und zuverlässiger als jeder existierende Headless Browser.
Kern-These: 80% der CPU-Zeit in Chrome/Firefox wird für Rendering,
Layout und Image-Decoding verschwendet — Dinge die kein Scraper braucht.
Durch Elimination dieser Komponenten + Rust-Arena-Allokation + Co-Prozess-Architektur
erreichen wir Faktor 40-50× Effizienzgewinn gegenüber Headless Chrome.
Architektur
┌──────────────────────────────────────────────────────────────┐
│ RUST CORE (Hauptprozess) │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────────┐ │
│ │ Event Loop │ │ DOM Engine │ │
│ │ (Tokio) │ │ ───────────────── │ │
│ │ │ │ html5ever → DOM Tree (Arena) │ │
│ │ Steuert: │ │ selectors crate → querySelector │ │
│ │ ├─ Timing │ │ Event Dispatch (click, submit...) │ │
│ │ ├─ Priorität │ │ MutationObserver in Rust │ │
│ │ ├─ JS-Timeout│ │ DOM Snapshot → Shared Buffer │ │
│ │ └─ Ressource │ └──────────────────────────────────────┘ │
│ │ │ │
│ │ ┌──────────────────────────────────────┐ │
│ │ │ Network Stack │ │
│ │ │ ───────────────── │ │
│ │ │ rustls + hyper (H1/H2/H3) │ │
│ │ │ TLS Fingerprinting (Chrome-Level) │ │
│ │ │ Proxy Rotation (SOCKS5/HTTP) │ │
│ │ │ Cookie Jar (RFC 6265) │ │
│ │ │ Brotli / ZSTD Dekompression │ │
│ │ └──────────────────────────────────────┘ │
│ │ │
│ └──────────────────┬──────────────────────────────────────────┘
│ │ Unix Domain Socket + Shared Memory
│ ▼
│ ┌──────────────────────────────────────────────────────────────┐
│ │ BUN/JSC CO-PROZESS (JS Execution) │
│ │ ──────────────────────── │
│ │ JavaScriptCore (JSC) Runtime │
│ │ Service Workers / Web Workers │
│ │ Fake Canvas 2D + WebGL (lazy emulation → Fingerprinting) │
│ │ Fake Web Audio API │
│ │ Fake Geolocation / Sensors / Battery │
│ │ setTimeout / setInterval / requestAnimationFrame │
│ │ IntersectionObserver / ResizeObserver (fake) │
│ │ navigator.mediaDevices (fake) │
│ │ IndexedDB / localStorage / sessionStorage │
│ └──────────────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────────────┘
Performance-Ziele vs Headless Chrome
| Metrik | Headless Chrome | Rust Headless Browser | Faktor |
|---|---|---|---|
| RAM pro Seite | 150-300 MB | 5-15 MB | 20× weniger |
| DOM Parse (100KB) | 15-30ms | 2-4ms (html5ever) | 7× schneller |
| querySelector(".price") | ~0.5ms | ~0.02ms (selectors) | 25× schneller |
| DOM Memory (50K Nodes) | ~120 MB (V8) | ~3 MB (Arena) | 40× weniger |
| Seiten/min (einfach) | ~50 | ~2.000+ | 40× mehr |
| Seiten/min (SPA) | ~10 | ~200+ | 20× mehr |
| Startzeit | 2-5s | ~50ms | 50× schneller |
| 100 parallele Seiten | 15-30 GB RAM | 0.5-1.5 GB RAM | 20× weniger |
| Kosten pro 1M Seiten | ~4.000€ | ~100€ | 40× günstiger |
Anti-Bot / Fingerprinting
| Technik | Ansatz |
|---|---|
| TLS Fingerprint | Rustls mit Chrome-Handshake + Cipher-Order + GREASE |
| HTTP/2 Settings | Exakte Chrome-H2-Frames |
| TCP Timing | Keine "Bot"-Latenzmuster (TCP Fast Open) |
| User-Agent | Beliebig konfigurierbar (Chrome/Firefox/Safari) |
| Navigator APIs | Fake WebGL, Canvas, Audio → deterministische Werte |
| WebDriver Flag | navigator.webdriver === false |
| Screen/Battery | Realistische Fake-Werte |
| JS Stack Traces | Keine Rust-Frames im JS-Callstack |
| Proxy Chain | SOCKS5 + Residential IPs |
Ziel: Cloudflare, DataDome, Akamai, PerimeterX erkennen KEINEN Headless Browser.
→ Gleiche Trefferquote wie Chrome, aber zu 2% der Kosten.
Migrationspfad vom Vorgänger (true-headless-browser)
Das alte Projekt ist 2 Tage alt und enthält bereits funktionierenden Code,
der in die neue Architektur überführt wird:
| Komponente | Alter Status | Neue Architektur |
|---|---|---|
| html5ever napi | ✅ Funktioniert | → Rust Core (direkt, ohne napi) |
| rustls + hyper napi | ✅ Funktioniert | → Rust Core (direkt, ohne napi) |
| DOM Implementation | ✅ Happy DOM fork + OwnWindow | → Rust DOM (Arena) + JSC Bridge |
| Canvas/WebGL/Audio Fake | ✅ JS-Implementierung | → Bleibt in JSC (Bun Co-Prozess) |
| Service Worker | ✅ Bun/JSC nativ | → Bleibt in JSC (Bun Co-Prozess) |
| Event Loop | ✅ Bun/JSC nativ | → Rust Tokio (Hauptschleife) |
| SpecProxy | ✅ JS-Implementierung | → Optional in JSC |
| DOM Snapshot | ❌ Neu | → Rust Shared Buffer |
| Batch Pipeline | ❌ Neu | → Rust Tokio Tasks |
Projektstruktur
rust-headless-browser/
├── Cargo.toml # Workspace
├── crates/
│ ├── core/ # Event Loop, Scheduler, Prozess-Orchestrierung
│ ├── dom/ # DOM Tree (Arena), Node, Element, Text
│ ├── html-parser/ # html5ever Wrapper → Rust DOM
│ ├── css-selectors/ # selectors crate Wrapper
│ ├── network/ # rustls + hyper H1/H2/H3, Proxy, Cookie Jar
│ ├── tls-fingerprint/ # Chrome-TLS-Handshake + GREASE
│ ├── js-bridge/ # Unix Socket + Shared Memory → Bun/JSC
│ └── scraper/ # Batch-Pipeline, DOM Snapshot, Extraktion
├── js-runtime/ # Bun/JSC Co-Prozess
│ ├── package.json
│ ├── src/
│ │ ├── index.ts # Socket-Listener, RPC-Handler
│ │ ├── fakes/ # Canvas, WebGL, Audio, etc.
│ │ ├── workers/ # Service Worker, Web Worker Wrapper
│ │ └── bridge/ # DOM Proxy (Rust-gebundene DOM-API)
│ └── tests/
├── tests/ # Integrationstests
│ ├── corpus/ # Testseiten (Amazon, Google, etc.)
│ └── spec/ # WPT (Web Platform Tests)
└── README.md
Roadmap
Phase 1 — Grundgerüst (Woche 1)
rust-headless-browserRepo erstellen- Rust Binary + Bun Co-Prozess Start
- Unix Domain Socket Kommunikation
- html5ever → DOM Tree in Arena
- selectors crate für querySelector
Phase 2 — JS Integration (Woche 2)
- JSC-Bridge: JS kann DOM lesen (RPC)
- Event Dispatch (click, submit) von Rust
- Fake Canvas/WebGL/Audio aus altem Projekt importieren
- Service Worker + Web Worker in Bun
Phase 3 — Anti-Bot + Scraping (Woche 3)
- TLS Fingerprinting (Chrome-Level)
- Cookie Jar + Proxy Rotation
- DOM Snapshot → Shared Buffer
- Batch-Scraping Pipeline (Tokio Tasks)
Phase 4 — Korpus + Stabilität (Woche 4)
- 100 Testseiten (Amaze, Google, Shop, SPA)
- Cloudflare/DataDome Integrationstests
- Performance-Benchmarks vs Chrome
- Dokumentation + Beispiele
Prinzipien
-
0 Pixel. Kein Paint, kein Layout, kein Rendering.
Alle visuellen APIs existieren (Canvas, WebGL, CSSOM) — aber sie tun nichts. -
Spec-konformes DOM.
innerHTML,querySelector,getComputedStyle,
Events, MutationObserver — alles nach WHATWG-Standard. -
Voller JS-Stack. Service Worker, Web Worker, ES Modules,
dynamic imports — alles was Chrome kann, können wir auch. -
Rust bestimmt. Der Event-Loop, das Timing, die Ressourcen-Allokation —
alles in Rust. JS ist nur ein Gast in unserer Engine. -
Kein Chrome-Code. Kein CDP, kein Blink, kein V8.
Wir bauen das von Grund auf für unseren Use-Case optimiert. -
Batch-first. Jede Seite ist ein Tokio-Task.
par_iter()über 1000 URLs. Kein Limit außer RAM.
Warum Rust + Bun statt Chrome/Firefox?
| Chrome | Firefox | Rust Headless | |
|---|---|---|---|
| Codebase | ~25M LOC | ~20M LOC | ~50K LOC |
| DOM Memory | V8 GC ~150MB | SpiderMonkey ~120MB | Arena ~3MB |
| Event-Loop | V8 kontrolliert | SM kontrolliert | DU kontrollierst |
| TLS Fingerprint | Patch-puppeteer | Patch-selenium | Native Rust |
| Startzeit | 2-5s | 1-3s | ~50ms |
| RAM/Seite | 150-300MB | 100-250MB | 5-15MB |
| Seiten/min | ~50 | ~40 | ~2.000+ |
| Antibut-Preis | ~0.004€/Seite | ~0.005€/Seite | ~0.0001€/Seite |
Lizenz
MIT — mach damit was du willst.