from flask import Flask, render_template, request, jsonify, redirect, url_for import asyncio import aiohttp import os import json import time import random import string from datetime import datetime import multiprocessing from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor from threading import Lock import logging import sys from functools import partial # Configuration du logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(sys.stdout), logging.FileHandler('website_requester.log') ] ) logger = logging.getLogger('website_requester') app = Flask(__name__) # Variables globales pour stocker l'état, utilisation d'un gestionnaire multiprocessing manager = multiprocessing.Manager() requests_in_progress = manager.Value('b', False) progress_counter = manager.Value('i', 0) total_requests = manager.Value('i', 0) requests_being_made = manager.list() requests_lock = manager.Lock() # Pour sécuriser l'accès aux données partagées process_pool = None background_tasks = [] # Configuration de paramètres avancés MAX_CONNECTIONS = 1000 # Nombre maximal de connexions simultanées SAVE_FREQUENCY = 100 # Fréquence de sauvegarde des résultats (tous les X requêtes) CHUNK_SIZE = 5000 # Nombre de requêtes à traiter par processus MAX_RETRIES = 5 # Nombre maximum de tentatives pour chaque opération RETRY_DELAY = 0.5 # Délai entre les tentatives en secondes REQUESTS_FILE = "requetes_gabaohub.json" IP_ROTATION_COUNT = 50 # Rotation des IP après ce nombre de requêtes REQUEST_TIMEOUT = 15 # Timeout des requêtes en secondes def generate_gabonese_ip(): """Génère une adresse IP du Gabon (plage 41.158.0.0/16)""" return f"41.158.{random.randint(0, 255)}.{random.randint(1, 254)}" def get_random_user_agent(): """Retourne un User-Agent aléatoire parmi les plus courants""" user_agents = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/99.0.1150.55", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36", "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1", "Mozilla/5.0 (iPad; CPU OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" ] return random.choice(user_agents) def get_random_referrer(): """Retourne un référent aléatoire plausible""" referrers = [ "https://www.google.com/", "https://www.bing.com/", "https://www.yahoo.com/", "https://duckduckgo.com/", "https://www.facebook.com/", "https://twitter.com/", "https://www.linkedin.com/", "https://www.instagram.com/", "https://www.gabon.ga/", "https://www.gov.ga/", "https://www.youtube.com/", "", # Aucun référent (accès direct) ] return random.choice(referrers) async def with_retries(func, *args, max_retries=MAX_RETRIES, **kwargs): """Exécute une fonction avec plusieurs tentatives en cas d'échec""" for attempt in range(max_retries): try: return await func(*args, **kwargs) except Exception as e: if attempt == max_retries - 1: logger.error(f"Échec définitif après {max_retries} tentatives: {str(e)}") raise delay = RETRY_DELAY * (2 ** attempt) # Backoff exponentiel logger.warning(f"Tentative {attempt+1} échouée: {str(e)}. Nouvel essai dans {delay:.2f}s") await asyncio.sleep(delay) async def create_session(request_counter): """Crée une session HTTP optimisée avec rotation d'IP""" # Configurer la session avec un meilleur contrôle des connexions connector = aiohttp.TCPConnector( limit=MAX_CONNECTIONS, # Nombre maximum de connexions simultanées ssl=False, # Désactiver la vérification SSL pour plus de performance force_close=False, # Réutiliser les connexions quand c'est possible use_dns_cache=True, # Utiliser le cache DNS ttl_dns_cache=300 # Cache DNS valide pendant 5 minutes ) timeout = aiohttp.ClientTimeout( total=REQUEST_TIMEOUT, connect=10, sock_connect=10, sock_read=10 ) session = aiohttp.ClientSession( connector=connector, timeout=timeout, headers={ "User-Agent": get_random_user_agent(), "X-Forwarded-For": generate_gabonese_ip(), "Referer": get_random_referrer(), "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7", "Accept-Encoding": "gzip, deflate, br", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1" } ) # Middleware pour faire une rotation des IPs et des User-Agents original_request = session._request async def request_middleware(method, url, **kwargs): nonlocal request_counter # Rotation des IPs, des User-Agents et des référents après un certain nombre de requêtes if request_counter.value % IP_ROTATION_COUNT == 0: kwargs.setdefault('headers', {}).update({ "X-Forwarded-For": generate_gabonese_ip(), "User-Agent": get_random_user_agent(), "Referer": get_random_referrer() }) request_counter.value += 1 return await original_request(method, url, **kwargs) session._request = request_middleware return session async def make_homepage_request(session, request_index, request_counter): """Effectue une requête vers la page d'accueil""" global requests_being_made try: # URL de la page d'accueil url = "https://gabaohub.alwaysdata.net" # Paramètres aléatoires pour simuler des utilisateurs réels params = {} # Ajouter des paramètres UTM aléatoires occasionnellement if random.random() < 0.3: # 30% de chance utm_sources = ["facebook", "twitter", "instagram", "direct", "google", "bing"] utm_mediums = ["social", "cpc", "email", "referral", "organic"] utm_campaigns = ["spring_promo", "launch", "awareness", "brand", "product"] params["utm_source"] = random.choice(utm_sources) params["utm_medium"] = random.choice(utm_mediums) params["utm_campaign"] = random.choice(utm_campaigns) # Mettre à jour les informations de la requête dans la liste with requests_lock: if request_index < len(requests_being_made): requests_being_made[request_index]["url"] = url requests_being_made[request_index]["params"] = params requests_being_made[request_index]["status"] = "in_progress" requests_being_made[request_index]["start_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # Effectuer la requête avec retries try: start_time = time.time() async with session.get(url, params=params, allow_redirects=True) as response: # Lire le contenu pour simuler le chargement complet de la page content = await response.read() end_time = time.time() response_time = end_time - start_time # Mettre à jour le statut with requests_lock: if request_index < len(requests_being_made): requests_being_made[request_index]["status"] = "success" requests_being_made[request_index]["status_code"] = response.status requests_being_made[request_index]["response_time"] = round(response_time, 3) requests_being_made[request_index]["content_length"] = len(content) requests_being_made[request_index]["end_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") progress_counter.value += 1 except Exception as e: with requests_lock: if request_index < len(requests_being_made): requests_being_made[request_index]["status"] = "failed" requests_being_made[request_index]["error"] = str(e) requests_being_made[request_index]["end_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") progress_counter.value += 1 except Exception as e: with requests_lock: if request_index < len(requests_being_made): requests_being_made[request_index]["status"] = "failed" requests_being_made[request_index]["error"] = str(e) requests_being_made[request_index]["end_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") progress_counter.value += 1 async def process_request_chunk(start_index, chunk_size, process_id=0): """Traite un groupe de requêtes dans un processus séparé""" logger.info(f"Processus {process_id}: Démarrage du traitement pour les indices {start_index} à {start_index+chunk_size-1}") # Créer un compteur de requêtes partagé request_counter = multiprocessing.Value('i', 0) # Créer une nouvelle session HTTP pour ce processus async with await create_session(request_counter) as session: # Créer un sémaphore pour limiter les connexions simultanées semaphore = asyncio.Semaphore(MAX_CONNECTIONS // multiprocessing.cpu_count()) async def process_request(i): request_index = start_index + i if request_index >= total_requests.value: return async with semaphore: await make_homepage_request(session, request_index, request_counter) # Pause aléatoire pour éviter les modèles de trafic détectables await asyncio.sleep(random.uniform(0.2, 2.0)) # Temps d'attente plus naturel entre les requêtes # Sauvegarde périodique if progress_counter.value % SAVE_FREQUENCY == 0: save_results_to_file() # Créer et exécuter toutes les tâches pour ce chunk tasks = [process_request(i) for i in range(min(chunk_size, total_requests.value - start_index))] await asyncio.gather(*tasks) logger.info(f"Processus {process_id}: Traitement terminé pour le chunk commençant à l'indice {start_index}") def save_results_to_file(): """Sauvegarde l'état actuel dans un fichier JSON de manière thread-safe""" with requests_lock: try: # Créer une copie des données actuelles data_to_save = list(requests_being_made) # Utiliser un fichier temporaire pour éviter la corruption temp_file = f"{REQUESTS_FILE}.tmp" with open(temp_file, "w", encoding="utf-8") as f: json.dump(data_to_save, f, indent=2, ensure_ascii=False) # Remplacer le fichier original par le fichier temporaire os.replace(temp_file, REQUESTS_FILE) logger.info(f"Sauvegarde effectuée: {progress_counter.value}/{total_requests.value} requêtes") except Exception as e: logger.error(f"Erreur lors de la sauvegarde: {str(e)}") def run_request_process(start_index, chunk_size, process_id): """Fonction exécutée dans chaque processus pour effectuer des requêtes""" try: # Configurer la nouvelle boucle asyncio pour ce processus loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) # Exécuter le traitement du chunk loop.run_until_complete(process_request_chunk(start_index, chunk_size, process_id)) loop.close() except Exception as e: logger.error(f"Erreur dans le processus {process_id}: {str(e)}") def start_request_process(num_requests, concurrency): """Démarre le processus d'envoi de requêtes avec multiprocessing""" global requests_in_progress, total_requests, progress_counter, requests_being_made, process_pool, background_tasks # Réinitialiser les compteurs with requests_lock: progress_counter.value = 0 total_requests.value = num_requests requests_being_made[:] = [{"status": "pending"} for _ in range(num_requests)] # Déterminer le nombre optimal de processus num_cpus = multiprocessing.cpu_count() num_processes = min(num_cpus, (num_requests + CHUNK_SIZE - 1) // CHUNK_SIZE) logger.info(f"Démarrage de l'envoi de {num_requests} requêtes avec {num_processes} processus et concurrence de {concurrency}") # Diviser le travail en chunks process_pool = ProcessPoolExecutor(max_workers=num_processes) background_tasks = [] for i in range(num_processes): start_idx = i * CHUNK_SIZE if start_idx < num_requests: task = process_pool.submit( run_request_process, start_idx, min(CHUNK_SIZE, num_requests - start_idx), i ) background_tasks.append(task) # Démarrer un thread de surveillance pour les tâches en arrière-plan monitor_thread = ThreadPoolExecutor(max_workers=1) monitor_thread.submit(monitor_background_tasks) def monitor_background_tasks(): """Surveille les tâches en arrière-plan et marque le processus comme terminé lorsque tout est fait""" global requests_in_progress, background_tasks try: # Attendre que toutes les tâches soient terminées for task in background_tasks: task.result() # Ceci bloquera jusqu'à ce que la tâche soit terminée logger.info(f"Toutes les tâches d'envoi de requêtes sont terminées. {progress_counter.value}/{total_requests.value} requêtes traitées.") # Sauvegarde finale save_results_to_file() # Marquer le processus comme terminé requests_in_progress.value = False except Exception as e: logger.error(f"Erreur lors de la surveillance des tâches: {str(e)}") requests_in_progress.value = False @app.route('/') def index(): """Page d'accueil""" # Charger les requêtes existantes si elles existent requests_data = [] if os.path.exists(REQUESTS_FILE): try: with open(REQUESTS_FILE, "r", encoding="utf-8") as f: requests_data = json.load(f) except json.JSONDecodeError: requests_data = [] return render_template('index.html', requests_in_progress=requests_in_progress.value, requests=requests_data[:1000], # Limiter pour des raisons de performance UI progress=progress_counter.value, total=total_requests.value) @app.route('/start', methods=['POST']) def start_requests(): """Démarrer l'envoi de requêtes""" if not requests_in_progress.value: # Récupérer les paramètres num_requests = int(request.form.get('num_requests', 1000)) concurrency = int(request.form.get('concurrency', MAX_CONNECTIONS)) # Marquer comme en cours requests_in_progress.value = True # Démarrer l'envoi de requêtes start_request_process(num_requests, concurrency) logger.info(f"Envoi de {num_requests} requêtes lancé avec concurrence {concurrency}") else: logger.warning("Un processus d'envoi de requêtes est déjà en cours") return redirect(url_for('index')) @app.route('/progress') def get_progress(): """Endpoint API pour obtenir la progression complète""" # Charger les dernières données requests_data = [] if os.path.exists(REQUESTS_FILE): try: with open(REQUESTS_FILE, "r", encoding="utf-8") as f: requests_data = json.load(f) except json.JSONDecodeError: requests_data = [] return jsonify({ 'requests_in_progress': requests_in_progress.value, 'progress': progress_counter.value, 'total': total_requests.value, 'requests': requests_data[:200] # Limiter pour des raisons de performance }) @app.route('/status') def get_status(): """Endpoint API simplifié pour obtenir juste la progression""" return jsonify({ 'requests_in_progress': requests_in_progress.value, 'progress': progress_counter.value, 'total': total_requests.value, 'success_count': sum(1 for req in requests_being_made if req.get('status') == 'success'), 'failed_count': sum(1 for req in requests_being_made if req.get('status') == 'failed'), 'pending_count': sum(1 for req in requests_being_made if req.get('status') == 'pending'), }) @app.route('/reset', methods=['POST']) def reset(): """Réinitialise le processus et supprime les données existantes""" global requests_in_progress, requests_being_made, progress_counter, total_requests, process_pool, background_tasks if not requests_in_progress.value: # Réinitialiser les compteurs et les données with requests_lock: requests_being_made[:] = [] progress_counter.value = 0 total_requests.value = 0 # Supprimer le fichier JSON s'il existe if os.path.exists(REQUESTS_FILE): os.remove(REQUESTS_FILE) logger.info("Réinitialisation effectuée") else: logger.warning("Impossible de réinitialiser pendant un processus en cours") return redirect(url_for('index')) @app.route('/stop', methods=['POST']) def stop_requests(): """Arrête le processus d'envoi de requêtes en cours""" global requests_in_progress, process_pool if requests_in_progress.value and process_pool: logger.info("Arrêt des processus d'envoi de requêtes...") process_pool.shutdown(wait=False) requests_in_progress.value = False # Sauvegarde l'état actuel save_results_to_file() logger.info("Processus d'envoi de requêtes arrêté") return redirect(url_for('index')) @app.route('/stats') def get_stats(): """Obtient des statistiques sur les requêtes effectuées""" requests_data = [] if os.path.exists(REQUESTS_FILE): try: with open(REQUESTS_FILE, "r", encoding="utf-8") as f: requests_data = json.load(f) except json.JSONDecodeError: requests_data = [] # Calculer les statistiques de base successful_requests = [req for req in requests_data if req.get('status') == 'success'] failed_requests = [req for req in requests_data if req.get('status') == 'failed'] # Temps de réponse moyen pour les requêtes réussies avg_response_time = 0 if successful_requests: avg_response_time = sum(req.get('response_time', 0) for req in successful_requests) / len(successful_requests) # Codes de statut des réponses status_codes = {} for req in successful_requests: code = req.get('status_code') if code: status_codes[code] = status_codes.get(code, 0) + 1 # Types d'erreurs pour les requêtes échouées error_types = {} for req in failed_requests: error = req.get('error', 'Unknown') error_type = error.split(':')[0] if ':' in error else error error_types[error_type] = error_types.get(error_type, 0) + 1 return jsonify({ 'total_requests': len(requests_data), 'successful_requests': len(successful_requests), 'failed_requests': len(failed_requests), 'avg_response_time': avg_response_time, 'status_codes': status_codes, 'error_types': error_types }) # Template HTML pour l'interface utilisateur @app.route('/templates/index.html') def get_template(): return """
0 / 0 requêtes traitées (0%)
État: Inactif
Aucune statistique disponible.
# | URL | Statut | Code | Temps (s) | Heure |
---|---|---|---|---|---|
Chargement des données... |