Files
2026-02-20 17:17:06 +01:00

215 lines
9.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os, json, logging, requests, re, time
from datetime import datetime
import pytz
from flask import Flask, render_template, redirect, url_for
from bs4 import BeautifulSoup
from apscheduler.schedulers.background import BackgroundScheduler
app = Flask(__name__)
# Formatage des logs pour inclure l'heure et le niveau
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger("CineSynthese")
CACHE_FILE = "/data/movies_cache.json"
HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'}
paris_tz = pytz.timezone("Europe/Paris")
def get_today_formats():
now = datetime.now(paris_tz)
jours_courts = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]
day_short = jours_courts[now.weekday()]
return {
"cosmo_id": f"seance-{now.strftime('%Y-%m-%d')}",
"cratere_date": f"{day_short}. {now.day:02d}/{now.month:02d}"
}
# --- 1. SCRAPER ABC ---
def scrape_abc():
url = "https://abc-toulouse.fr/horaires.html"
logger.info("📡 [ABC] Début du scraping...")
movies = []
try:
r = requests.get(url, headers=HEADERS, timeout=15)
soup = BeautifulSoup(r.content, 'html.parser')
movie_blocks = soup.find_all('div', class_='table_infos')
for block in movie_blocks:
title_tag = block.find('h2')
if not title_tag: continue
title = title_tag.get_text(strip=True)
img_tag = block.find('img')
img_url = "https://abc-toulouse.fr" + img_tag.get('src') if img_tag else ""
next_div = block.find_next_sibling()
showtimes_text = next_div.get_text(" ") if next_div else block.get_text(" ")
times = re.findall(r'(\d{2}:\d{2})', showtimes_text)
if times:
found_times = sorted(list(set(times)))
movies.append({
"title": title, "img": img_url, "times": found_times,
"cinema": "ABC", "color": "#00bcd4"
})
logger.info(f" ✅ [ABC] Trouvé: {title} ({', '.join(found_times)})")
logger.info(f"📊 [ABC] Total: {len(movies)} films récupérés.")
except Exception as e:
logger.error(f"❌ [ABC] Erreur lors du scraping: {e}")
return movies
# --- 2. SCRAPER LE CRATÈRE ---
def scrape_cratere():
url_main = "https://www.cinemalecratere.fr/films"
target_date = get_today_formats()["cratere_date"]
logger.info(f"📡 [Le Cratère] Début du scraping pour la date: {target_date}...")
movies_dict = {}
session = requests.Session()
try:
r = session.get(url_main, headers=HEADERS, timeout=15)
soup = BeautifulSoup(r.content, 'html.parser')
items = soup.find_all('div', attrs={'data-search': True})
for item in items:
if target_date in item['data-search']:
title_tag = item.find('span', class_='uk-text-bold')
title = title_tag.get_text(strip=True) if title_tag else "Inconnu"
time_match = re.search(r'(\d{2}h\d{2})', item['data-search'])
show_time = time_match.group(0).replace('h', ':') if time_match else "Séance"
if title not in movies_dict:
img_url = ""
link_tag = item.find('a', href=True)
if link_tag:
r_det = session.get("https://www.cinemalecratere.fr" + link_tag['href'], timeout=10)
img_tag = BeautifulSoup(r_det.content, 'html.parser').find('img', class_='el-image')
if img_tag:
img_url = img_tag.get('src')
if not img_url.startswith('http'): img_url = "https://www.cinemalecratere.fr" + img_url
movies_dict[title] = {"title": title, "img": img_url, "times": [show_time], "cinema": "Le Cratère", "color": "#e62117"}
logger.info(f" ✅ [Le Cratère] Nouveau film: {title}")
else:
if show_time not in movies_dict[title]["times"]:
movies_dict[title]["times"].append(show_time)
logger.info(f" [Le Cratère] Séance ajoutée pour {title}: {show_time}")
logger.info(f"📊 [Le Cratère] Total: {len(movies_dict)} films récupérés.")
except Exception as e:
logger.error(f"❌ [Le Cratère] Erreur lors du scraping: {e}")
return list(movies_dict.values())
# --- 3. SCRAPER COSMO ---
def scrape_cosmo():
BASE_URL = "https://www.american-cosmograph.fr"
url_horaires = f"{BASE_URL}/les-horaires.html"
target_id = get_today_formats()["cosmo_id"]
logger.info(f"📡 [Cosmograph] Début du scraping pour le bloc: {target_id}...")
movies_today = []
session = requests.Session()
try:
r = session.get(url_horaires, headers=HEADERS, timeout=15)
soup = BeautifulSoup(r.content, 'html.parser')
day_container = soup.find('div', id=target_id)
if not day_container:
logger.warning(f" ⚠️ [Cosmograph] Bloc {target_id} non trouvé sur le site.")
return []
items = day_container.find_all('li', class_='heureReady')
for item in items:
title_tag = item.select_one('.filmTitle a')
if not title_tag: continue
title = title_tag.get_text(strip=True)
detail_path = title_tag.get('href')
time_tag = item.select_one('.horaire_debut')
time_raw = time_tag.get_text(strip=True) if time_tag else ""
st_match = re.search(r'\d{2}h\d{2}', time_raw)
show_time = st_match.group(0).replace('h', ':') if st_match else "Séance"
img_url = ""
if detail_path:
try:
r_detail = session.get(f"{BASE_URL}{detail_path}", headers=HEADERS, timeout=10)
soup_detail = BeautifulSoup(r_detail.content, 'html.parser')
img_tag = soup_detail.find('img', class_='fc_field_image')
if img_tag:
img_url = img_tag.get('src')
if img_url and not img_url.startswith('http'):
img_url = f"{BASE_URL}{img_url}"
except: pass
movies_today.append({"title": title, "img": img_url, "times": [show_time], "cinema": "American Cosmograph", "color": "#ffc107"})
logger.info(f" ✅ [Cosmograph] Séance trouvée: {title} à {show_time}")
time.sleep(0.1)
except Exception as e:
logger.error(f"❌ [Cosmograph] Erreur lors du scraping: {e}")
unique_movies = {}
for m in movies_today:
if m['title'] not in unique_movies:
unique_movies[m['title']] = m
else:
if m['times'][0] not in unique_movies[m['title']]['times']:
unique_movies[m['title']]['times'].append(m['times'][0])
unique_movies[m['title']]['times'].sort()
logger.info(f"📊 [Cosmograph] Total: {len(unique_movies)} films uniques récupérés.")
return list(unique_movies.values())
# --- LOGIQUE MISE À JOUR ---
def update_all_data():
logger.info("🚀 --- LANCEMENT DE LA MISE À JOUR GLOBALE ---")
start_time = time.time()
try:
abc = scrape_abc()
cratere = scrape_cratere()
cosmo = scrape_cosmo()
data = abc + cratere + cosmo
data = sorted(data, key=lambda x: x['times'][0] if x['times'] else "99:99")
now_paris = datetime.now(paris_tz)
update_str = now_paris.strftime("%d/%m/%Y à %H:%M")
payload = {"last_update": update_str, "movies": data}
os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True)
with open(CACHE_FILE, 'w') as f: json.dump(payload, f)
duration = round(time.time() - start_time, 2)
logger.info(f"✨ --- MISE À JOUR TERMINÉE EN {duration}s ---")
logger.info(f"📈 Total final: {len(data)} films à l'affiche aujourd'hui.")
return payload
except Exception as e:
logger.error(f"💥 Erreur critique lors de la mise à jour: {e}")
return None
scheduler = BackgroundScheduler(timezone=paris_tz)
scheduler.add_job(func=update_all_data, trigger="cron", hour=0, minute=5)
scheduler.start()
@app.route('/')
def index():
if not os.path.exists(CACHE_FILE):
logger.info("️ Cache inexistant, lancement du premier scraping...")
cache = update_all_data()
else:
with open(CACHE_FILE, 'r') as f: cache = json.load(f)
if isinstance(cache, list):
logger.info("️ Cache ancien format détecté, conversion...")
cache = update_all_data()
return render_template('index.html', films=cache["movies"], updated=cache["last_update"])
@app.route('/refresh')
def refresh():
logger.info("🔄 Rafraîchissement manuel demandé via /refresh")
update_all_data()
return redirect(url_for('index'))
from flask import send_from_directory
@app.route('/manifest.json')
def serve_manifest():
return send_from_directory('static', 'manifest.json')
@app.route('/static/sw.js')
def serve_sw():
return send_from_directory('static', 'sw.js')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)