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 from jinja2 import Undefined # Pour gérer les valeurs Undefined # Définition d'une fonction pour convertir les objets Undefined en chaîne vide def default_json(o): if isinstance(o, Undefined): return '' raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") # Configuration du logger 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__) # Définir un encodeur JSON personnalisé pour Flask from flask.json import JSONEncoder class CustomJSONEncoder(JSONEncoder): def default(self, obj): if isinstance(obj, Undefined): return '' return super().default(obj) app.json_encoder = CustomJSONEncoder # 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 = [] # 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""" connector = aiohttp.TCPConnector( limit=MAX_CONNECTIONS, ssl=False, force_close=False, use_dns_cache=True, ttl_dns_cache=300 ) 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 la rotation des IP, User-Agent et référents original_request = session._request async def request_middleware(method, url, **kwargs): nonlocal request_counter 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 = "https://gabaohub.alwaysdata.net" params = {} if random.random() < 0.3: 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) 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") try: start_time = time.time() async with session.get(url, params=params, allow_redirects=True) as response: content = await response.read() end_time = time.time() response_time = end_time - start_time 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}") request_counter = multiprocessing.Value('i', 0) async with await create_session(request_counter) as session: 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) await asyncio.sleep(random.uniform(0.2, 2.0)) if progress_counter.value % SAVE_FREQUENCY == 0: save_results_to_file() 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: data_to_save = list(requests_being_made) 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, default=default_json) 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: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) 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 with requests_lock: progress_counter.value = 0 total_requests.value = num_requests requests_being_made[:] = [{"status": "pending"} for _ in range(num_requests)] 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}") 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) 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: for task in background_tasks: task.result() 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.") save_results_to_file() 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""" 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], 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: num_requests = int(request.form.get('num_requests', 1000)) concurrency = int(request.form.get('concurrency', MAX_CONNECTIONS)) requests_in_progress.value = True 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""" 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] }) @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: with requests_lock: requests_being_made[:] = [] progress_counter.value = 0 total_requests.value = 0 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 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 = [] 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'] avg_response_time = 0 if successful_requests: avg_response_time = sum(req.get('response_time', 0) for req in successful_requests) / len(successful_requests) status_codes = {} for req in successful_requests: code = req.get('status_code') if code: status_codes[code] = status_codes.get(code, 0) + 1 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 """ Générateur de requêtes pour gabaohub.alwaysdata.net

Générateur de requêtes pour gabaohub.alwaysdata.net

Contrôles
Progression 🔄

0 / 0 requêtes traitées (0%)

État: Inactif

Réussies

0

Échouées

0

En attente

0

Statistiques 🔄

Aucune statistique disponible.

Dernières requêtes 🔄
# URL Statut Code Temps (s) Heure
Chargement des données...
""" if __name__ == '__main__': app.run(debug=True)