Nye-TeeOff/kode_eksport_1/backend_scrape_greenfee_py.txt

173 lines
6.6 KiB
Text
Raw Normal View History

2026-04-10 09:52:34 +02:00
"""
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))