""" TEE OFF - GREENFEE-SKRAPER MED GEMINI AI --------------------------------------------------------------------------- Henter alle greenfee-varianter fra en (eller flere) URL-er og strukturerer dem i en JSON-liste. Finner også avtaleklubber/vennskapsklubber. --------------------------------------------------------------------------- """ import asyncio import asyncpg import os import json import argparse from bs4 import BeautifulSoup from playwright.async_api import async_playwright import google.generativeai as genai from dotenv import load_dotenv load_dotenv() DB_URL = os.getenv("DATABASE_URL", "postgresql://teeoff_admin:teeoff_secret_password@db:5432/teeoff") GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") if not GEMINI_API_KEY: raise ValueError("🚨 GEMINI_API_KEY mangler i .env filen!") genai.configure(api_key=GEMINI_API_KEY) model = genai.GenerativeModel('gemini-2.5-flash') async def fetch_page_text(url: str, browser) -> str: url = url.strip() if not url.startswith("http"): return "" print(f" 🌐 Laster inn: {url}") try: page = await browser.new_page() await page.goto(url, wait_until="domcontentloaded", timeout=15000) html_content = await page.content() await page.close() soup = BeautifulSoup(html_content, 'html.parser') for script in soup(["script", "style", "nav", "footer", "header"]): script.extract() return soup.get_text(separator=' ', strip=True) except Exception as e: print(f" ❌ Feil ved lasting av {url}: {e}") return "" def analyze_greenfee_with_gemini(text: str, club_name: str) -> dict: print(f" 🧠 Sender {len(text)} tegn til Gemini for greenfee-analyse...") prompt = f""" Du er en ekspert på norske golfklubber og prissetting. Din oppgave er å lese teksten hentet fra nettsidene til "{club_name}" og hente ut TO ting: 1. ALLE varianter av greenfee-priser. 2. Navn på eventuelle vennskapsklubber/avtaleklubber (hvis nevnt). REGLER FOR GREENFEE: - Trekk ut absolutt alle priskategorier du finner (f.eks. "Hverdag høysesong", "Helg før kl 14", "Gjest av medlem", "9 hull kveld", osv.). - Finn både voksenpris og juniorpris for hver kategori. - HVIS juniorpris er oppgitt som en regel (f.eks. "Juniorer betaler halv pris" eller "50% rabatt for junior"), MÅ du selv regne ut prisen og skrive inn heltallet. - "banenavn": Bruk navnet på banen hvis det er spesifisert (f.eks. "18-hullsbanen", "Korthullsbanen"). Hvis ikke spesifisert, bruk "{club_name}". - Priser SKAL være tall (integer). Sett pris til null (null) hvis den ikke finnes. REGLER FOR AVTALEKLUBBER: - Let etter overskrifter som "Vennskapsklubber", "Avtaleklubber", "Gjestespill", "Samarbeidsklubber". - Trekk ut kun navnene på klubbene i en liste (f.eks. ["Haga GK", "Oslo GK"]). La listen være tom hvis du ikke finner noen. TEKST FRA NETTSIDEN: {text} OPPGAVE: Returner KUN et gyldig JSON-objekt med nøyaktig følgende struktur: {{ "foreslatt_greenfee": [ {{ "banenavn": "Navn på banen", "priskategori": "F.eks: Hverdag Gjest av Medlem", "pris_voksne": 600, "pris_junior": 300 }} ], "foreslatt_avtaleklubber": [ "Klubb 1 GK", "Klubb 2 GK" ], "ai_begrunnelse": "Kort forklaring, f.eks: 'Fant et komplekst prissystem for høy/lavsesong. Regnet ut juniorpriser til 50% som angitt i teksten. Fant 3 samarbeidsklubber nederst.'" }} """ try: response = model.generate_content(prompt) raw_response = response.text.strip() if raw_response.startswith("```json"): raw_response = raw_response[7:] if raw_response.endswith("```"): raw_response = raw_response[:-3] return json.loads(raw_response.strip()) except Exception as e: print(f" ❌ AI-analyse feilet: {e}") return None async def run_greenfee_scraper(facility_ids=None): print("🚀 Starter Greenfee-skraperen...") conn = await asyncpg.connect(DB_URL) try: query = "SELECT id, name, greenfee_url FROM facilities WHERE greenfee_url IS NOT NULL AND greenfee_url != ''" if facility_ids: query += f" AND id IN ({','.join(map(str, facility_ids))})" facilities = await conn.fetch(query) print(f"📋 Fant {len(facilities)} anlegg å skrape.") async with async_playwright() as p: browser = await p.chromium.launch(headless=True) for facility in facilities: fac_id = facility['id'] name = facility['name'] urls_raw = facility['greenfee_url'] print(f"\n▶️ Behandler Greenfee for: {name} (ID: {fac_id})") urls = [u.strip() for u in urls_raw.split(',')] combined_text = "" for idx, url in enumerate(urls, 1): page_text = await fetch_page_text(url, browser) if page_text: combined_text += f"\n\n--- TEKST FRA SIDE {idx} ({url}) ---\n{page_text}" if len(combined_text) < 50: print(" ⚠️ Fant for lite tekst, hopper over.") continue draft_data = analyze_greenfee_with_gemini(combined_text[:25000], name) if not draft_data: continue funnet_priser = len(draft_data.get('foreslatt_greenfee', [])) funnet_klubber = len(draft_data.get('foreslatt_avtaleklubber', [])) print(f" ✅ AI fant {funnet_priser} greenfee-varianter og {funnet_klubber} avtaleklubber.") await conn.execute(""" UPDATE facilities SET greenfee_draft = $1::jsonb WHERE id = $2 """, json.dumps(draft_data), fac_id) print(" 💾 Greenfee-utkast lagret i databasen!") await browser.close() finally: await conn.close() print("\n🏁 Skraping fullført.") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Skrap greenfeepriser via AI.") parser.add_argument("--ids", type=str, help="Kommaseparert liste med facility IDs (eks: 1,5,12)") args = parser.parse_args() ids_to_scrape = None if args.ids: ids_to_scrape = [int(x.strip()) for x in args.ids.split(",")] asyncio.run(run_greenfee_scraper(ids_to_scrape))