diff --git a/app.py b/app.py index 1365086..a941879 100644 --- a/app.py +++ b/app.py @@ -6,13 +6,13 @@ 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'} -# --- CONFIGURATION FUSEAU HORAIRE --- paris_tz = pytz.timezone("Europe/Paris") def get_today_formats(): @@ -24,9 +24,10 @@ def get_today_formats(): "cratere_date": f"{day_short}. {now.day:02d}/{now.month:02d}" } -# --- 1. SCRAPER ABC (Ton code précis) --- +# --- 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) @@ -35,7 +36,7 @@ def scrape_abc(): for block in movie_blocks: title_tag = block.find('h2') if not title_tag: continue - title = title_tag.get_text().strip() + 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 "" @@ -43,17 +44,22 @@ def scrape_abc(): 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": sorted(list(set(times))), + "title": title, "img": img_url, "times": found_times, "cinema": "ABC", "color": "#00bcd4" }) - except: logger.error("Fail ABC") + 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 (Ton code précis) --- +# --- 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: @@ -66,6 +72,7 @@ def scrape_cratere(): 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) @@ -75,24 +82,34 @@ def scrape_cratere(): 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) - except: logger.error("Fail Cratère") + 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 (REPRISE STRICTE DE TON CODE app_cosmo.py) --- +# --- 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: return [] + 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') @@ -103,11 +120,11 @@ def scrape_cosmo(): 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: - detail_url = f"{BASE_URL}{detail_path}" try: - r_detail = session.get(detail_url, headers=HEADERS, timeout=10) + 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: @@ -115,9 +132,12 @@ def scrape_cosmo(): 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: pass + except Exception as e: + logger.error(f"❌ [Cosmograph] Erreur lors du scraping: {e}") unique_movies = {} for m in movies_today: @@ -127,27 +147,37 @@ def scrape_cosmo(): 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("🕒 Mise à jour programmée...") + logger.info("🚀 --- LANCEMENT DE LA MISE À JOUR GLOBALE ---") + start_time = time.time() try: - data = scrape_abc() + scrape_cratere() + scrape_cosmo() + 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") - # Heure de mise à jour calée sur Paris now_paris = datetime.now(paris_tz) - payload = {"last_update": now_paris.strftime("%d/%m/%Y à %H:%M"), "movies": data} + 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 MAJ: {e}") + logger.error(f"💥 Erreur critique lors de la mise à jour: {e}") return None -# --- SCHEDULER CALÉ SUR PARIS --- scheduler = BackgroundScheduler(timezone=paris_tz) scheduler.add_job(func=update_all_data, trigger="cron", hour=0, minute=5) scheduler.start() @@ -155,14 +185,18 @@ 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): cache = update_all_data() + 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'))