Nye-TeeOff/kode_eksport_1/backend_scrape_greenfee_py.txt
2026-04-10 09:52:34 +02:00

173 lines
No EOL
6.6 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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))