# πŸ“Š TLD Price Tracking System - Implementierungsplan ## Übersicht Dieses Dokument beschreibt den Plan zur Implementierung eines automatischen TLD-Preis-Tracking-Systems, das Preisdaten ΓΌber 12 Monate sammelt und korrekt abbildet. **🎯 Fokus: 100% Kostenlos & UnabhΓ€ngig** --- ## πŸ” Wie funktioniert das Domain-Preis-Γ–kosystem? ### Die Preiskette verstehen ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ WIE DOMAIN-PREISE ENTSTEHEN β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ 1️⃣ REGISTRY (z.B. Verisign fΓΌr .com) β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β€’ Verwaltet die TLD technisch β”‚ β”‚ β”‚ β”‚ β€’ Setzt den WHOLESALE-PREIS (von ICANN reguliert) β”‚ β”‚ β”‚ β”‚ β€’ .com = $9.59 Wholesale (2024) β”‚ β”‚ β”‚ β”‚ β€’ Diese Preise sind Γ–FFENTLICH in ICANN-VertrΓ€gen! β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ ↓ β”‚ β”‚ 2️⃣ REGISTRAR (Namecheap, GoDaddy, Cloudflare...) β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β€’ Kauft Domains zum Wholesale-Preis β”‚ β”‚ β”‚ β”‚ β€’ Verkauft an Endkunden mit MARGE β”‚ β”‚ β”‚ β”‚ β€’ .com = $10-15 Retail (je nach Registrar) β”‚ β”‚ β”‚ β”‚ β€’ Preise auf ihren Websites Γ–FFENTLICH sichtbar β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ ↓ β”‚ β”‚ 3️⃣ AGGREGATOREN (TLD-List.com, DomComp...) β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ WIE SIE AN DIE DATEN KOMMEN: β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ a) πŸ’° AFFILIATE-PROGRAMME (Hauptquelle!) β”‚ β”‚ β”‚ β”‚ β†’ Registrare geben Affiliates Zugang zu Preis-Feeds β”‚ β”‚ β”‚ β”‚ β†’ TLD-List verdient Provision pro Referral β”‚ β”‚ β”‚ β”‚ β†’ Kostenlos, aber erfordert Traffic/Registrierung β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ b) πŸ”— RESELLER-APIs β”‚ β”‚ β”‚ β”‚ β†’ Als Reseller bekommt man API-Zugang β”‚ β”‚ β”‚ β”‚ β†’ Erfordert Mindestguthaben (~$100-500) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ c) 🌐 WEB SCRAPING β”‚ β”‚ β”‚ β”‚ β†’ Γ–ffentliche Preisseiten automatisch auslesen β”‚ β”‚ β”‚ β”‚ β†’ Technisch einfach, rechtlich grauzone β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ d) πŸ“‹ Γ–FFENTLICHE REGISTRY-DATEN β”‚ β”‚ β”‚ β”‚ β†’ ICANN verΓΆffentlicht Wholesale-Preise β”‚ β”‚ β”‚ β”‚ β†’ Basis fΓΌr Preisberechnungen β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Warum kΓΆnnen TLD-List & Co. alle Preise zeigen? | Methode | Wie es funktioniert | FΓΌr uns nutzbar? | |---------|---------------------|------------------| | **Affiliate-Programme** | Registrare geben Preis-Feeds an Partner, die Traffic bringen | ⭐ JA - Kostenlos, erfordert Anmeldung | | **Reseller-Status** | Als Reseller bekommt man API-Zugang | ⚠️ Erfordert Mindesteinzahlung | | **Web Scraping** | Γ–ffentliche Seiten auslesen | ⭐ JA - Sofort mΓΆglich | | **Direkte Partnerschaften** | Business-Deals mit Registraren | ❌ Nur fΓΌr grosse Player | --- ## 🎯 Ziele 1. **Automatisierte Datensammlung** - Cronjob crawlt tΓ€glich/wΓΆchentlich Preise von mehreren Registraren 2. **Historische Daten** - 12 Monate Preisverlauf pro TLD und Registrar 3. **Echte Marktdaten** - Keine generierten/simulierten Daten 4. **Lokal & Server** - Funktioniert identisch auf beiden Umgebungen 5. **πŸ†“ Kostenlos** - Keine API-Keys, keine externen AbhΓ€ngigkeiten --- ## πŸ› οΈ Unsere Optionen (von einfach bis komplex) ### Option A: Web Scraping (Sofort umsetzbar) ⭐ EMPFOHLEN ``` Aufwand: 2-3 Tage | Kosten: $0 | ZuverlΓ€ssigkeit: ⭐⭐⭐⭐ ``` **So funktioniert's:** - Registrare zeigen ihre Preise ΓΆffentlich auf ihren Websites - Wir lesen diese Seiten automatisch aus (BeautifulSoup) - Aggregator-Seiten wie TLD-List.com haben bereits alles zusammengetragen **Quellen:** | Quelle | Was wir bekommen | Schwierigkeit | |--------|------------------|---------------| | TLD-List.com | ~1500 TLDs, 50+ Registrare | ⭐ Sehr einfach | | Porkbun.com | Direkte Preise | ⭐ Sehr einfach | | Spaceship.com | Direkte Preise | ⭐ Einfach | --- ### Option B: Affiliate-Programme (Beste Langzeit-LΓΆsung) ⭐⭐ ``` Aufwand: 1 Woche | Kosten: $0 | ZuverlΓ€ssigkeit: ⭐⭐⭐⭐⭐ ``` **So funktioniert's:** - Registrare wie Namecheap, Porkbun, etc. haben Affiliate-Programme - Als Affiliate bekommst du Zugang zu strukturierten Preis-Feeds - Du verdienst sogar Provision wenn jemand ΓΌber deinen Link kauft! **VerfΓΌgbare Affiliate-Programme:** | Registrar | Affiliate-Programm | Preis-Feed? | |-----------|-------------------|-------------| | **Namecheap** | namecheap.com/support/affiliates | βœ… CSV/XML Feed | | **Porkbun** | porkbun.com/affiliate | βœ… API Zugang | | **Dynadot** | dynadot.com/community/affiliate | βœ… Preis-API | | **NameSilo** | namesilo.com/affiliate | βœ… Bulk-Preise | --- ### Option C: Reseller-API (Professionellste LΓΆsung) ``` Aufwand: 1-2 Wochen | Kosten: $100-500 Einzahlung | ZuverlΓ€ssigkeit: ⭐⭐⭐⭐⭐ ``` **So funktioniert's:** - Werde Reseller bei einem GrosshΓ€ndler (ResellerClub, eNom, OpenSRS) - Du bekommst vollstΓ€ndigen API-Zugang zu allen TLD-Preisen - Einmalige Mindesteinzahlung erforderlich **Reseller-Plattformen:** | Plattform | Mindesteinzahlung | API-QualitΓ€t | |-----------|-------------------|--------------| | ResellerClub | ~$100 | ⭐⭐⭐⭐⭐ | | eNom | ~$250 | ⭐⭐⭐⭐ | | OpenSRS | ~$500 | ⭐⭐⭐⭐⭐ | --- ### Option D: Offizielle Registry-Daten (FΓΌr Wholesale-Preise) ``` Aufwand: 1 Tag | Kosten: $0 | Was: Nur Wholesale-Preise ``` **So funktioniert's:** - ICANN verΓΆffentlicht Registry-VertrΓ€ge mit Wholesale-Preisen - Verisign (.com), PIR (.org), etc. - alle Preise sind ΓΆffentlich - Retail-Preise = Wholesale + Registrar-Marge **Γ–ffentliche Quellen:** | Quelle | URL | Daten | |--------|-----|-------| | ICANN Contracts | icann.org/resources/agreements | Wholesale-Preise | | IANA Root DB | iana.org/domains/root/db | TLD-Liste + Registry | --- ## πŸ“Š Datenquellen (100% Kostenlos) ### ⭐ Tier 1: Aggregator-Seiten (BESTE OPTION - Eine Quelle fΓΌr alles!) | Quelle | URL | Vorteile | Scraping | |--------|-----|----------|----------| | **TLD-List.com** | https://tld-list.com/ | Alle TLDs, alle Registrare, Vergleichstabellen | ⭐ Sehr einfach | | **DomComp** | https://www.domcomp.com/ | Preisvergleich, Historische Daten | Einfach | **Warum TLD-List.com die beste Wahl ist:** - βœ… ~1500 TLDs abgedeckt - βœ… 50+ Registrare verglichen - βœ… Saubere HTML-Struktur (einfach zu parsen) - βœ… RegelmΓ€ssig aktualisiert - βœ… Keine Login/Auth erforderlich - βœ… Keine API-Rate-Limits ### Tier 2: Direkte Registrar-Preisseiten (Backup/Validation) | Registrar | URL | Format | Schwierigkeit | |-----------|-----|--------|---------------| | **Porkbun** | https://porkbun.com/products/domains | HTML-Tabelle | ⭐ Sehr einfach | | **Namecheap** | https://www.namecheap.com/domains/domain-pricing/ | JS-Rendered | Mittel | | **Cloudflare** | https://www.cloudflare.com/products/registrar/ | "At-cost" Liste | Einfach | | **Spaceship** | https://www.spaceship.com/pricing/ | HTML-Tabelle | Einfach | | **NameSilo** | https://www.namesilo.com/pricing | HTML-Tabelle | Einfach | ### Tier 3: Offizielle Quellen (Validation/Reference) | Quelle | URL | Daten | |--------|-----|-------| | **IANA** | https://www.iana.org/domains/root/db | Offizielle TLD-Liste | | **ICANN** | https://www.icann.org/resources/pages/registries | Registry-Informationen | --- ## πŸ—οΈ Systemarchitektur (API-Frei) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ TLD Price Tracking System (100% Free) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ APScheduler │────▢│ Web Scraper Service β”‚ β”‚ β”‚ β”‚ (Cronjob) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ - Daily: 03:00 β”‚ β”‚ β”‚ TLD-List.com Scraper β”‚ β”‚ β”‚ β”‚ β”‚ - Weekly: Sun β”‚ β”‚ β”‚ (Hauptquelle - alle TLDs) β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ Porkbun Scraper (Backup) β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ Spaceship Scraper (Backup) β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Data Aggregation & Validation β”‚ β”‚ β”‚ β”‚ - Cross-check prices from multiple sources β”‚ β”‚ β”‚ β”‚ - Detect outliers (>20% change = warning) β”‚ β”‚ β”‚ β”‚ - Calculate confidence score β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ SQLite/PostgreSQL β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ tld_prices β”‚ β”‚ tld_info β”‚ β”‚ scrape_logs β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ (history) β”‚ β”‚ (metadata) β”‚ β”‚ (audit trail) β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ FastAPI Backend β”‚ β”‚ β”‚ β”‚ - GET /tld-prices/overview (cached + fresh) β”‚ β”‚ β”‚ β”‚ - GET /tld-prices/{tld}/history (12 Monate echte Daten) β”‚ β”‚ β”‚ β”‚ - GET /tld-prices/trending (echte Trends aus DB) β”‚ β”‚ β”‚ β”‚ - POST /admin/scrape (manueller Trigger) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Warum Web Scraping die beste LΓΆsung ist: | Aspekt | APIs | Web Scraping | |--------|------|--------------| | **Kosten** | Teils kostenpflichtig | βœ… Kostenlos | | **API Keys** | Erforderlich | βœ… Nicht nΓΆtig | | **Rate Limits** | Streng | βœ… Selbst kontrolliert | | **AbhΓ€ngigkeit** | Von Anbieter | βœ… UnabhΓ€ngig | | **StabilitΓ€t** | API-Γ„nderungen | HTML-Γ„nderungen (selten) | | **Abdeckung** | Nur ein Registrar | βœ… Alle via Aggregator | --- ## πŸ“ Neue Dateien & Struktur ``` backend/ β”œβ”€β”€ app/ β”‚ β”œβ”€β”€ services/ β”‚ β”‚ β”œβ”€β”€ tld_scraper/ # NEU: Scraper Package β”‚ β”‚ β”‚ β”œβ”€β”€ __init__.py β”‚ β”‚ β”‚ β”œβ”€β”€ base.py # Basis-Klasse fΓΌr Scraper β”‚ β”‚ β”‚ β”œβ”€β”€ tld_list.py # TLD-List.com Scraper (Haupt) β”‚ β”‚ β”‚ β”œβ”€β”€ porkbun.py # Porkbun Scraper (Backup) β”‚ β”‚ β”‚ β”œβ”€β”€ spaceship.py # Spaceship Scraper (Backup) β”‚ β”‚ β”‚ β”œβ”€β”€ validator.py # Cross-Validation β”‚ β”‚ β”‚ └── aggregator.py # Kombiniert alle Quellen β”‚ β”‚ β”‚ β”‚ β”‚ └── scheduler.py # NEU: APScheduler Service β”‚ β”‚ β”‚ β”œβ”€β”€ models/ β”‚ β”‚ └── tld_price.py # Bereits vorhanden βœ“ β”‚ β”‚ └── scrape_log.py # NEU: Audit Logs β”‚ β”‚ β”‚ └── api/ β”‚ └── tld_prices.py # Anpassen fΓΌr echte DB-Daten β”‚ β”œβ”€β”€ scripts/ β”‚ └── seed_initial_prices.py # NEU: Initial-Daten Seed β”‚ └── manual_scrape.py # NEU: Manueller Scrape β”‚ └── .env # Nur Scraper-Settings (keine API Keys!) ``` --- ## πŸ”§ Konfiguration (Keine API Keys nΓΆtig!) ### `.env` Erweiterung ```env # ===== TLD Price Scraper (100% Kostenlos) ===== # Scraper Settings SCRAPER_ENABLED=true SCRAPER_SCHEDULE_DAILY_HOUR=3 # Uhrzeit (UTC) SCRAPER_SCHEDULE_WEEKLY_DAY=sun # VollstΓ€ndiger Scrape SCRAPER_MAX_RETRIES=3 SCRAPER_TIMEOUT_SECONDS=30 SCRAPER_DELAY_BETWEEN_REQUESTS=2 # Sekunden zwischen Requests # User-Agent Rotation (Optional fΓΌr Stealth) SCRAPER_USER_AGENTS="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36,Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" # Quellen aktivieren/deaktivieren SCRAPER_SOURCE_TLDLIST=true # Hauptquelle SCRAPER_SOURCE_PORKBUN=true # Backup SCRAPER_SOURCE_SPACESHIP=true # Backup # Validation SCRAPER_MAX_PRICE_CHANGE_PERCENT=20 # Warnung bei >20% Γ„nderung SCRAPER_MIN_PRICE_USD=0.50 # Minimum gΓΌltiger Preis SCRAPER_MAX_PRICE_USD=500 # Maximum gΓΌltiger Preis ``` --- ## πŸ“… Crawl-Strategie ### Wann crawlen? | Frequenz | Zeit | Warum | |----------|------|-------| | **TΓ€glich** | 03:00 UTC | Niedrige Server-Last, frische Daten fΓΌr neuen Tag | | **WΓΆchentlich** | Sonntag 03:00 | VollstΓ€ndiger Crawl aller TLDs und Registrare | | **Bei Bedarf** | Manual Trigger | Admin kann manuell crawlen | ### Was crawlen? | PrioritΓ€t | TLDs | Frequenz | |-----------|------|----------| | **Hoch** | com, net, org, io, co, ai, app, dev | TΓ€glich | | **Mittel** | info, biz, xyz, me, cc, tv | 2x pro Woche | | **Niedrig** | Alle anderen (~500 TLDs) | WΓΆchentlich | ### Daten pro Crawl ```python { "tld": "com", "registrar": "cloudflare", "registration_price": 10.44, "renewal_price": 10.44, "transfer_price": 10.44, "promo_price": null, "currency": "USD", "recorded_at": "2024-12-08T03:00:00Z", "source": "api", # oder "scrape" "confidence": 1.0 # 0.0-1.0 } ``` --- ## πŸ—ƒοΈ Datenbank-Schema Erweiterung ### Neues Model: `CrawlLog` ```python class CrawlLog(Base): """Audit trail fΓΌr Crawler-AktivitΓ€ten.""" __tablename__ = "crawl_logs" id: Mapped[int] = mapped_column(primary_key=True) # Crawl Info started_at: Mapped[datetime] completed_at: Mapped[datetime | None] status: Mapped[str] # running, success, partial, failed # Statistics tlds_crawled: Mapped[int] = mapped_column(default=0) registrars_crawled: Mapped[int] = mapped_column(default=0) prices_collected: Mapped[int] = mapped_column(default=0) errors_count: Mapped[int] = mapped_column(default=0) # Details error_details: Mapped[str | None] = mapped_column(Text, nullable=True) source_breakdown: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON ``` ### Erweiterung `TLDPrice` ```python # ZusΓ€tzliche Felder source: Mapped[str] = mapped_column(String(20), default="api") # api, scrape, manual confidence: Mapped[float] = mapped_column(Float, default=1.0) # 0.0-1.0 crawl_log_id: Mapped[int | None] = mapped_column(ForeignKey("crawl_logs.id"), nullable=True) ``` --- ## πŸ”§ Implementierung (Web Scraping) ### Phase 1: Basis-Infrastruktur (Tag 1) 1. **Scheduler Service erstellen** ```python # backend/app/services/scheduler.py from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger scheduler = AsyncIOScheduler() def setup_scheduler(): # Daily scrape at 03:00 UTC scheduler.add_job( scrape_tld_prices, CronTrigger(hour=3, minute=0), id="daily_tld_scrape", replace_existing=True ) scheduler.start() ``` 2. **Scraper Base Class** ```python # backend/app/services/tld_scraper/base.py from abc import ABC, abstractmethod from dataclasses import dataclass @dataclass class TLDPriceData: tld: str registrar: str registration_price: float renewal_price: float | None transfer_price: float | None currency: str = "USD" source: str = "scrape" class BaseTLDScraper(ABC): name: str base_url: str @abstractmethod async def scrape(self) -> list[TLDPriceData]: """Scrape prices from the source.""" pass async def health_check(self) -> bool: """Check if source is accessible.""" async with httpx.AsyncClient() as client: response = await client.get(self.base_url, timeout=10) return response.status_code == 200 ``` ### Phase 2: TLD-List.com Scraper (Tag 2) - HAUPTQUELLE **Warum TLD-List.com?** - Aggregiert Preise von 50+ Registraren - ~1500 TLDs abgedeckt - Saubere HTML-Tabellen-Struktur - Keine JavaScript-Rendering nΓΆtig ```python # backend/app/services/tld_scraper/tld_list.py from bs4 import BeautifulSoup import httpx import asyncio class TLDListScraper(BaseTLDScraper): """Scraper fΓΌr tld-list.com - die beste kostenlose Quelle.""" name = "tld-list" base_url = "https://tld-list.com" # URLs fΓΌr verschiedene TLD-Kategorien ENDPOINTS = { "all": "/tlds-from-a-z/", "new": "/new-tlds/", "cheapest": "/cheapest-tlds/", } async def scrape(self) -> list[TLDPriceData]: results = [] async with httpx.AsyncClient() as client: # Alle TLDs scrapen response = await client.get( f"{self.base_url}{self.ENDPOINTS['all']}", headers={"User-Agent": self.get_user_agent()}, timeout=30 ) soup = BeautifulSoup(response.text, "lxml") # TLD-Tabelle finden table = soup.find("table", {"class": "tld-table"}) if not table: raise ScraperError("TLD table not found") for row in table.find_all("tr")[1:]: # Skip header cells = row.find_all("td") if len(cells) >= 4: tld = cells[0].text.strip().lstrip(".") # Preise von verschiedenen Registraren extrahieren price_cell = cells[1] # Registration price registrar_link = price_cell.find("a") if registrar_link: price = self.parse_price(price_cell.text) registrar = registrar_link.get("data-registrar", "unknown") results.append(TLDPriceData( tld=tld, registrar=registrar, registration_price=price, renewal_price=self.parse_price(cells[2].text), transfer_price=self.parse_price(cells[3].text), )) return results def parse_price(self, text: str) -> float | None: """Parse price from text like '$9.99' or '€8.50'.""" import re match = re.search(r'[\$€£]?([\d,]+\.?\d*)', text.replace(",", "")) return float(match.group(1)) if match else None def get_user_agent(self) -> str: """Rotate user agents to avoid detection.""" import random agents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", ] return random.choice(agents) ``` ### Phase 3: Backup Scraper (Tag 3) ```python # backend/app/services/tld_scraper/porkbun.py class PorkbunScraper(BaseTLDScraper): """Backup: Direkt von Porkbun scrapen.""" name = "porkbun" base_url = "https://porkbun.com/products/domains" async def scrape(self) -> list[TLDPriceData]: async with httpx.AsyncClient() as client: response = await client.get( self.base_url, headers={"User-Agent": self.get_user_agent()}, timeout=30 ) soup = BeautifulSoup(response.text, "lxml") results = [] # Porkbun hat eine saubere Tabellen-Struktur for tld_div in soup.find_all("div", {"class": "tldRow"}): tld = tld_div.find("span", {"class": "tld"}).text.strip() price = tld_div.find("span", {"class": "price"}).text.strip() results.append(TLDPriceData( tld=tld.lstrip("."), registrar="porkbun", registration_price=self.parse_price(price), renewal_price=None, # Separate Abfrage nΓΆtig transfer_price=None, )) return results ``` ### Phase 4: Aggregator & Validation (Tag 4) ```python # backend/app/services/tld_scraper/aggregator.py class TLDPriceAggregator: """Kombiniert alle Scraper und validiert Daten.""" def __init__(self): self.scrapers = [ TLDListScraper(), # Hauptquelle PorkbunScraper(), # Backup SpaceshipScraper(), # Backup ] async def run_full_scrape(self, db: AsyncSession) -> ScrapeLog: log = ScrapeLog(started_at=datetime.utcnow(), status="running") all_prices: dict[str, list[TLDPriceData]] = {} for scraper in self.scrapers: try: prices = await scraper.scrape() for price in prices: key = f"{price.tld}_{price.registrar}" if key not in all_prices: all_prices[key] = [] all_prices[key].append(price) log.sources_scraped += 1 except Exception as e: log.errors.append(f"{scraper.name}: {str(e)}") # Validierung: Cross-check zwischen Quellen validated_prices = self.validate_prices(all_prices) # In DB speichern await self.save_to_db(db, validated_prices) log.prices_collected = len(validated_prices) log.completed_at = datetime.utcnow() log.status = "success" if not log.errors else "partial" return log def validate_prices(self, prices: dict) -> list[TLDPriceData]: """Cross-validate prices from multiple sources.""" validated = [] for key, price_list in prices.items(): if len(price_list) == 1: # Nur eine Quelle - verwenden mit niedrigerem Confidence price = price_list[0] price.confidence = 0.7 validated.append(price) else: # Mehrere Quellen - Durchschnitt bilden avg_price = sum(p.registration_price for p in price_list) / len(price_list) # PrΓΌfen ob Preise Γ€hnlich sind (max 10% Abweichung) is_consistent = all( abs(p.registration_price - avg_price) / avg_price < 0.1 for p in price_list ) result = price_list[0] result.registration_price = avg_price result.confidence = 0.95 if is_consistent else 0.8 validated.append(result) return validated ``` --- ## πŸ–₯️ Lokal vs Server ### Lokal (Development) ```bash # .env.local CRAWLER_ENABLED=true CRAWLER_SCHEDULE_DAILY_HOUR=* # Jede Stunde zum Testen DATABASE_URL=sqlite+aiosqlite:///./domainwatch.db ``` ```bash # Manueller Test curl -X POST http://localhost:8000/api/v1/admin/crawl-prices ``` ### Server (Production) ```bash # .env CRAWLER_ENABLED=true CRAWLER_SCHEDULE_DAILY_HOUR=3 DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/pounce # Docker Compose services: backend: environment: - CRAWLER_ENABLED=true # APScheduler lΓ€uft im selben Container ``` ### Systemd Service (ohne Docker) ```ini # /etc/systemd/system/pounce-crawler.service [Unit] Description=Pounce TLD Price Crawler After=network.target [Service] Type=simple User=pounce WorkingDirectory=/opt/pounce/backend ExecStart=/opt/pounce/backend/venv/bin/python -m app.services.scheduler Restart=always [Install] WantedBy=multi-user.target ``` --- ## πŸ“ˆ API Endpoints (Angepasst) ```python # Echte historische Daten statt generierte @router.get("/{tld}/history") async def get_tld_price_history(tld: str, db: Database, days: int = 365): """Echte 12-Monats-Daten aus der Datenbank.""" result = await db.execute( select(TLDPrice) .where(TLDPrice.tld == tld) .where(TLDPrice.recorded_at >= datetime.utcnow() - timedelta(days=days)) .order_by(TLDPrice.recorded_at) ) prices = result.scalars().all() # Gruppiere nach Datum und berechne Durchschnitt return aggregate_daily_prices(prices) ``` --- ## πŸ“¦ AbhΓ€ngigkeiten ### Neue requirements.txt EintrΓ€ge ```txt # Web Scraping (Hauptmethode - KOSTENLOS!) beautifulsoup4>=4.12.0 lxml>=5.0.0 # Optional: FΓΌr JavaScript-lastige Seiten (nur wenn nΓΆtig) # playwright>=1.40.0 # Gross, nur bei Bedarf aktivieren # Rate Limiting (respektvoller Scraping) aiolimiter>=1.1.0 # Bereits vorhanden: # httpx>=0.28.0 βœ“ # apscheduler>=3.10.4 βœ“ ``` **GesamtgrΓΆsse der neuen Dependencies: ~2MB** (minimal!) --- ## πŸš€ Implementierungs-Zeitplan (Schneller ohne APIs!) | Phase | Dauer | Beschreibung | |-------|-------|--------------| | **1** | 1 Tag | Scheduler + DB Schema + Base Scraper | | **2** | 1 Tag | TLD-List.com Scraper (Hauptquelle) | | **3** | 0.5 Tag | Porkbun + Spaceship Backup Scraper | | **4** | 0.5 Tag | Aggregator + Validation | | **5** | 1 Tag | API Endpoints + Frontend Anpassung | | **6** | 1 Tag | Testing + Initial Scrape | **Total: ~5 Tage** (schneller als mit APIs!) --- ## ⚠️ Wichtige Hinweise ### Respektvolles Scraping ```python from aiolimiter import AsyncLimiter import asyncio # Max 30 Requests pro Minute (respektvoll) scrape_limiter = AsyncLimiter(30, 60) async def scrape_with_limit(url: str): async with scrape_limiter: # ZufΓ€llige VerzΓΆgerung zwischen Requests await asyncio.sleep(random.uniform(1, 3)) return await make_request(url) ``` ### Robots.txt Compliance ```python # Vor dem Scrapen prΓΌfen async def check_robots_txt(base_url: str) -> bool: """Check if scraping is allowed.""" robots_url = f"{base_url}/robots.txt" # ... parse robots.txt # TLD-List.com erlaubt Scraping (kein Disallow fΓΌr relevante Pfade) ``` ### Error Handling ```python class ScraperError(Exception): """Basis-Exception fΓΌr Scraper-Fehler.""" pass class HTMLStructureChanged(ScraperError): """Website-Struktur hat sich geΓ€ndert - Scraper muss angepasst werden.""" pass class RateLimitDetected(ScraperError): """Zu viele Requests - warten und erneut versuchen.""" retry_after: int = 300 # 5 Minuten ``` ### DatenqualitΓ€t - **Confidence Score**: - 0.95 = Mehrere Quellen stimmen ΓΌberein - 0.80 = Nur eine Quelle oder kleine Abweichungen - 0.70 = Unsicher (grosse Abweichungen) - **Outlier Detection**: Warnung bei >20% PreisΓ€nderung in 24h - **Validation**: Preis muss zwischen $0.50 und $500 liegen --- ## πŸ”’ Sicherheit & Best Practices 1. **Keine API Keys nΓΆtig** βœ… (Web Scraping ist 100% kostenlos) 2. **User-Agent Rotation**: Wechselnde Browser-IdentitΓ€ten 3. **Rate Limiting**: Max 30 req/min, 2-3 Sekunden Delay 4. **Robots.txt**: Immer respektieren 5. **Backup**: TΓ€gliches Datenbank-Backup 6. **Monitoring**: Alert bei Scraper-Fehlern (HTML-Γ„nderungen) --- ## πŸ§ͺ Robustheit-Strategien ### HTML-Struktur-Γ„nderungen erkennen ```python async def validate_page_structure(soup: BeautifulSoup) -> bool: """PrΓΌfen ob erwartete Elemente noch existieren.""" expected_elements = [ ("table", {"class": "tld-table"}), ("th", {"text": "TLD"}), ("th", {"text": "Price"}), ] for tag, attrs in expected_elements: if not soup.find(tag, attrs): return False return True ``` ### Fallback-Chain ``` TLD-List.com (Haupt) ↓ (falls Fehler) Porkbun (Backup 1) ↓ (falls Fehler) Spaceship (Backup 2) ↓ (falls Fehler) Letzte bekannte Daten aus DB verwenden + Alert ``` --- ## βœ… NΓ€chste Schritte (Kein API-Setup nΓΆtig!) 1. [ ] Datenbank-Migration fΓΌr neue Felder 2. [ ] Scheduler Service implementieren 3. [ ] TLD-List.com Scraper entwickeln 4. [ ] Backup Scraper (Porkbun, Spaceship) 5. [ ] Aggregator + Validation 6. [ ] API Endpoints anpassen 7. [ ] Initialen Scrape durchfΓΌhren 8. [ ] Frontend fΓΌr echte historische Daten aktualisieren --- ## πŸ’° KostenΓΌbersicht | Posten | Kosten | |--------|--------| | API Keys | $0 βœ… | | Externe Services | $0 βœ… | | Server-Ressourcen | Minimal (1x tΓ€glich ~100 Requests) | | Wartungsaufwand | ~1h/Monat (HTML-Γ„nderungen prΓΌfen) | **Total: $0/Monat** πŸŽ‰ --- **Soll ich mit der Implementierung beginnen?**