---
name: kiwi-browser-setup
description: Headless Chromium Browser im Docker-Container auf ZimaOS mit Playwright — echte Maus-Klicks, Tippen, Scrollen, Screenshots
trigger:
  - "browser"
  - "chromium"
  - "chrome"
  - "web scraping"
  - "headless browser"
  - "docker browser"
priority: high
---

# Kiwi Browser Setup v2 (Playwright)

Auf ZimaOS gibt es keinen Paketmanager und kein Desktop-Environment. Chromium läuft in einem Debian-Docker-Container mit **Playwright** — das ermöglicht echte native Maus-Klicks, Tastatureingaben, Scrollen und JavaScript-Ausführung.

## Architektur

- **Host**: ZimaOS (ZimaCube) mit Docker
- **Container**: `kiwi-browser` (Debian Bookworm Slim)
- **Engine**: Playwright + Chromium Headless
- **Anti-Detection**: `--disable-blink-features=AutomationControlled`, `navigator.webdriver` entfernt
- **API**: AioHTTP-Async-Server auf Port 3000 (extern 30000)

## Aktueller Status

Container läuft als `kiwi-browser:v2`. Image enthält Playwright + Chromium.

```bash
curl -s http://127.0.0.1:30000/health
# → {"status": "ok", "version": "v2-playwright"}
```

## Schnell-Setup (Neuinstallation)

```bash
# 1. Debian-Container starten
sudo docker run -d --name kiwi-browser debian:bookworm-slim sleep 3600

# 2. Pakete installieren
sudo docker exec kiwi-browser apt-get update -qq
sudo docker exec kiwi-browser sh -c 'DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends chromium python3 python3-pip curl ca-certificates fonts-liberation && rm -rf /var/lib/apt/lists/*'

# 3. Playwright + aiohttp installieren
sudo docker exec kiwi-browser pip3 install --break-system-packages playwright aiohttp
sudo docker exec kiwi-browser playwright install chromium

# 4. Server kopieren (v2)
sudo docker cp /DATA/AppData/hermes/skills/devops/kiwi-browser-setup/scripts/kiwi-browser-server-v2.py kiwi-browser:/app/server-v2.py

# 5. Committen & starten
sudo docker stop kiwi-browser
sudo docker commit kiwi-browser kiwi-browser:v2
sudo docker rm kiwi-browser
sudo docker run -d \
  --name kiwi-browser \
  --restart unless-stopped \
  -p 0.0.0.0:30000:3000 \
  kiwi-browser:v2 \
  bash -c 'cd /app && python3 server-v2.py > /app/server-v2.log 2>&1'
```

## API-Endpunkte

| Methode | Endpoint | Beschreibung |
|---------|----------|--------------|
| GET | `/health` | Status + Version |
| POST | `/navigate` | Zu URL navigieren |
| POST | `/visit` | Seite laden + Text extrahieren |
| POST | `/screenshot` | Screenshot als Base64 |
| POST | `/click` | **Echter Maus-Klick** auf Element (CSS-Selektor) oder Koordinaten (x,y) |
| POST | `/type` | **Text in Input-Feld tippen** + optional Enter drücken |
| POST | `/scroll` | **Scrollen** (down/up/bottom/top/to-selector) |
| POST | `/evaluate` | **JavaScript** im Browser ausführen |

### Beispiele

**Screenshot:**
```bash
curl -s -X POST http://127.0.0.1:30000/screenshot \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","full_page":true}' \
  | jq -r '.screenshot_base64' | base64 -d > screenshot.png
```

**Echter Maus-Klick:**
```bash
curl -s -X POST http://127.0.0.1:30000/click \
  -H "Content-Type: application/json" \
  -d '{"url":"https://de.wikipedia.org","selector":"a[href=\"/wiki/Meshtastic\"]","screenshot":true}'
```

**Text tippen + Submit:**
```bash
curl -s -X POST http://127.0.0.1:30000/type \
  -H "Content-Type: application/json" \
  -d '{"url":"https://duckduckgo.com","selector":"#searchbox_input","text":"meshtastic","submit":true}'
```

**Scrollen:**
```bash
curl -s -X POST http://127.0.0.1:30000/scroll \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","direction":"bottom"}'
```

**JavaScript ausführen:**
```bash
curl -s -X POST http://127.0.0.1:30000/evaluate \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","script":"document.title"}'
```

## Was mit echten Klicks möglich ist

| Aufgabe | Wie |
|---------|-----|
| YouTube Studio: Video hochladen | `/type` in Upload-Feld, `/click` auf Buttons |
| Webseiten durchklicken | `/click` auf Links, Menüs, Tabs |
| Formulare ausfüllen | `/type` + `/click` auf Submit |
| Infinite Scroll laden | `/scroll` direction: bottom |
| Cookie-Banner schließen | `/click` auf Accept-Button |
| Dropdowns öffnen | `/click` auf Select, dann Option |
| Captchas (einfache) | `/click` auf Checkboxen |

## Anti-Detection (eingebaut)

- `--disable-blink-features=AutomationControlled`
- `navigator.webdriver` → `undefined`
- Gefälschte Plugins-Array
- `window.chrome = { runtime: {} }`
- Deutsche Locale + Zeitzone Wien
- Viewport 1920×1080

## Structured Site Shortcuts
Before spinning up the browser for scraping, check if the target site exposes a clean JSON API. Reddit threads, for example, return full structured data by appending `.json` to the URL:

```bash
curl -s -A "Mozilla/5.0" "https://www.reddit.com/r/LocalLLaMA/comments/XXXX/.../.json" \
  | python3 -m json.tool > thread.json
```

This avoids HTML parsing, anti-bot measures, and renders irrelevant. Use the browser only when interaction (clicks, scrolls, form submission) is actually required.

### Reddit JSON API — Deep Extraction Pattern

When a Reddit thread is large (100+ comments), use the `.json` endpoint and Python for structured extraction instead of parsing HTML:

```python
import json, urllib.request

url = "https://www.reddit.com/r/SUBREDDIT/comments/POSTID/SLUG.json"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
data = json.loads(urllib.request.urlopen(req).read())

post = data[0]['data']['children'][0]['data']        # title, selftext, author, ups
comments = data[1]['data']['children']               # top-level

def extract(comments, depth=0, max_depth=5):
    out = []
    for c in comments:
        if c.get('kind') != 't1': continue
        d = c['data']
        out.append({"author": d['author'], "ups": d['ups'], "body": d.get('body','')[:500], "depth": depth})
        if depth < max_depth and d.get('replies') and isinstance(d['replies'], dict):
            out.extend(extract(d['replies']['data']['children'], depth+1, max_depth))
    return out

all_comments = extract(comments)
```

**Key fields:** `title`, `selftext`, `author`, `ups`, `num_comments` (post); `body`, `author`, `ups`, `replies` (recursive for nested threads).

## Bekannte Limits

| Limit | Ursache | Workaround |
|-------|---------|------------|
| Amazon blockt hart | TLS-Fingerprint, IP-Rate-Limit | Proxy-Rotation (geplant) |
| reCAPTCHA v3 | Verhaltens-Analyse | Menschliche Pausen einbauen |
| YouTube Upload | Komplexer Flow, Upload-Widget | Schritt-für-Schritt mit Pausen |
| Keine Cookie-Persistenz | Stateless Container | Docker-Volume für `/root/.config/chromium` mounten |
| JS-Heavy / SPA Sites (Reddit, X, etc.) | `/visit` liefert leeres `extracted`, weil Content client-seitig hydratisiert | Siehe **SPA-Scraping** unten und `references/spa-scraping.md` |

### SPA-Scraping (JS-Heavy Sites)

Seiten wie Reddit, Twitter/X oder moderne React/Vue-Apps liefern oft nur einen leeren App-Shell-HTML-Blob. `/visit` mit `wait: "networkidle"` reicht dann nicht.

**Strategien (von einfach zu robust):**

1. **Native JSON-API nutzen** — Reddit: einfach `.json` an die URL hängen (`https://reddit.com/r/.../comments/xxx.json?tl=de`). Liefert strukturierte Comments/Posts ohne Rendering.
2. **`/evaluate` mit `innerText`** — Nach Navigation: `{"script": "document.body.innerText"}`
3. **`wait: "load"` + Scroll-Trigger** — Manche SPAs laden erst nach Scroll. `/scroll` → `/evaluate` kombinieren.
4. **Screenshot + OCR** — Wenn Text extrahiert werden muss und API fehlt: `/screenshot` → OCR (z.B. `marker-pdf` oder `ocr-and-documents` Skill).

Siehe `references/spa-scraping.md` für ausführliche Rezepte pro Plattform.

## Fehlerbehebung

```bash
# Logs
sudo docker exec kiwi-browser cat /app/server-v2.log | tail -20

# Container neustarten
sudo docker restart kiwi-browser

# Image neu bauen
sudo docker rm -f kiwi-browser
sudo docker run -d --name kiwi-browser --restart unless-stopped -p 0.0.0.0:30000:3000 kiwi-browser:v2 bash -c 'cd /app && python3 server-v2.py > /app/server-v2.log 2>&1'
```

## Wichtige Dateien

| Datei | Pfad |
|-------|------|
| Container-Image | `kiwi-browser:v2` |
| v2 Server | `/app/server-v2.py` im Container |
| Logs | `/app/server-v2.log` im Container |
| Playwright-Chromium | `/root/.cache/ms-playwright/` im Container |

## Erweiterungen (geplant)

- Proxy-Unterstützung für IP-Rotation
- Cookie-Persistenz über Docker-Volume
- Mehrere Browser-Instanzen für Parallelität
- YouTube-Upload-Automatisierung (schrittweise)
