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

161 lines
No EOL
6.3 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 - VEIEN TIL GOLF (VTG) SKRAPER MED GEMINI AI
---------------------------------------------------------------------------
Henter pris, beskrivelse (inkl. lånekøller/medlemskap) og kursdatoer fra VTG-sider.
Støtter kommaseparerte URL-er.
---------------------------------------------------------------------------
"""
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_vtg_with_gemini(text: str, club_name: str) -> dict:
print(f" 🧠 Sender {len(text)} tegn til Gemini for VTG-analyse...")
prompt = f"""
Du er en ekspert på norske golfklubber. Din oppgave er å lese en lang tekst fra nettsidene til "{club_name}" og koke dette ned til essensen om deres "Veien til Golf" (VTG) nybegynnerkurs.
OPPGAVER:
1. Finn standardprisen for VTG-kurset for en vanlig voksen person. (Returner KUN tallet).
2. Skriv en KOMPRIMERT, selgende beskrivelse (maks 3-4 setninger). Du MÅ inkludere informasjon om:
- Er lån av køller/utstyr inkludert i kurset?
- Inkluderer prisen et medlemskap/spillerett i klubben (og ev. for hvor lenge)?
- Hva er omfanget? (F.eks. "12 timer praksis pluss e-læring").
Ignorer uvesentlig støy og lange historiske utgreiinger.
3. Finn alle kommende kursdatoer. Finn startdato/sluttdato for hvert kurs, og noter status ("Ledig", "Fulltegnet", "Venteliste").
TEKST FRA NETTSIDEN:
{text}
OPPGAVE:
Returner KUN et gyldig JSON-objekt med nøyaktig følgende struktur:
{{
"foreslatt_vtg_pris": 1990,
"foreslatt_vtg_beskrivelse": "Kurset går over 12 timer inkludert obligatorisk e-læring. Lån av golfkøller er inkludert under hele kurset, og prisen gir deg også fritt spill og medlemskap ut året.",
"foreslatt_vtg_datoer": [
{{"dato": "12.-14. mai", "status": "Fulltegnet"}},
{{"dato": "5.-7. juni", "status": "Ledig"}}
],
"ai_begrunnelse": "Fant voksenpris på 1990,-. Teksten nevnte eksplisitt at medlemskap ut året er med i prisen, og at man får låne utstyr."
}}
Merk: Sett foreslatt_vtg_pris til null (null) hvis du ikke finner den. Hvis du ikke finner datoer, la listen være tom [].
"""
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_vtg_scraper(facility_ids=None):
print("🚀 Starter Veien til Golf (VTG) skraperen...")
conn = await asyncpg.connect(DB_URL)
try:
query = "SELECT id, name, vtg_lenke FROM facilities WHERE vtg_lenke IS NOT NULL AND vtg_lenke != ''"
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['vtg_lenke']
print(f"\n▶ Behandler VTG 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_vtg_with_gemini(combined_text[:25000], name)
if not draft_data:
continue
print(f" ✅ AI fant pris: {draft_data.get('foreslatt_vtg_pris')}, og {len(draft_data.get('foreslatt_vtg_datoer', []))} datoer.")
await conn.execute("""
UPDATE facilities
SET vtg_draft = $1::jsonb
WHERE id = $2
""", json.dumps(draft_data), fac_id)
print(" 💾 VTG-utkast lagret i databasen!")
await browser.close()
finally:
await conn.close()
print("\n🏁 Skraping fullført.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Skrap VTG 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_vtg_scraper(ids_to_scrape))