import time import json from selenium import webdr...
Créé le : 30 novembre 2025
Répondu en utilisant GPT-5.1 Thinking par Chat01
Créé le : 30 novembre 2025
Répondu en utilisant GPT-5.1 Thinking par Chat01
import time
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from bs4 import BeautifulSoup
import pandas as pd
from webdriver_manager.chrome import ChromeDriverManager
class FlashscoreParser:
def init(self, headless=True):
self.setup_driver(headless)
textdef setup_driver(self, headless): """Настройка Chrome драйвера""" chrome_options = Options() if headless: chrome_options.add_argument("--headless") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--disable-blink-features=AutomationControlled") chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) chrome_options.add_experimental_option('useAutomationExtension', False) chrome_options.add_argument( "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ) # Инициализация драйвера service = Service(ChromeDriverManager().install()) self.driver = webdriver.Chrome(service=service, options=chrome_options) self.driver.execute_script( "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" ) def parse_match_stats(self, url): """Парсинг статистики матча""" try: print(f"Загружаем страницу: {url}") self.driver.get(url) # Ждем загрузки основных элементов WebDriverWait(self.driver, 15).until( EC.presence_of_element_located( (By.CSS_SELECTOR, '.duelParticipant__home .participant__participantName') ) ) WebDriverWait(self.driver, 15).until( EC.presence_of_element_located((By.CSS_SELECTOR, '[data-testid="wcl-statistics"]')) ) # Даем время для полной загрузки time.sleep(3) soup = BeautifulSoup(self.driver.page_source, 'html.parser') # Основная информация о матче match_info = self._parse_match_info(soup) # Статистика по секциям stats_data = self._parse_statistics(soup) # Анализ игры команд analysis = self.analyze_performance(match_info, stats_data) return { 'match_info': match_info, 'statistics': stats_data, 'analysis': analysis } except Exception as e: print(f"Ошибка при парсинге: {e}") # Сохраняем HTML для отладки with open('debug_page.html', 'w', encoding='utf-8') as f: f.write(self.driver.page_source) print("Сохранен debug_page.html для анализа") return None def _parse_match_info(self, soup): """Парсинг основной информации о матче""" match_info = {} try: # Команды home_team_elem = soup.select_one('.duelParticipant__home .participant__participantName') away_team_elem = soup.select_one('.duelParticipant__away .participant__participantName') home_team = home_team_elem.get_text(strip=True) if home_team_elem else 'N/A' away_team = away_team_elem.get_text(strip=True) if away_team_elem else 'N/A' # Счет score_wrapper = soup.find('div', class_='detailScore__wrapper') if score_wrapper: score_spans = score_wrapper.find_all('span') if len(score_spans) >= 3: score = f"{score_spans[0].get_text(strip=True)}-{score_spans[2].get_text(strip=True)}" else: score = score_wrapper.get_text(strip=True) else: score = 'N/A' # Дата и время date_elem = soup.find('div', class_='duelParticipant__startTime') date_time = date_elem.get_text(strip=True) if date_elem else 'N/A' # Турнир breadcrumbs = soup.find('nav', class_='wcl-breadcrumbs_0ZcSd') if breadcrumbs: tournament_elem = breadcrumbs.find_all('li')[-1] tournament = tournament_elem.get_text(strip=True) if tournament_elem else 'N/A' else: tournament = 'N/A' # Статус матча status_elem = soup.find('span', class_='fixedHeaderDuel__detailStatus') status = status_elem.get_text(strip=True) if status_elem else 'Finished' match_info = { 'home_team': home_team, 'away_team': away_team, 'score': score, 'date_time': date_time, 'tournament': tournament, 'status': status } except Exception as e: print(f"Ошибка при парсинге основной информации: {e}") return match_info def _parse_statistics(self, soup): """Парсинг статистических данных""" stats_data = {} try: # Находим все секции со статистикой sections = soup.find_all('div', class_='section') for section in sections: section_title = section.find('div', class_='section__title') if not section_title: continue title = section_title.get_text(strip=True) stats_data[title] = [] # Находим все строки статистики в секции stat_rows = section.find_all('div', attrs={'data-testid': 'wcl-statistics'}) for row in stat_rows: stat_item = self._parse_stat_row(row) if stat_item: stats_data[title].append(stat_item) except Exception as e: print(f"Ошибка при парсинге статистики: {e}") return stats_data def _parse_stat_row(self, row): """Парсинг отдельной строки статистики""" try: # Название показателя category_elem = row.find('div', class_='wcl-category_6sT1J') if not category_elem: return None category = category_elem.get_text(strip=True) # Значения для домашней и гостевой команд home_value_elem = row.find('div', class_='wcl-homeValue_3Q-7P') away_value_elem = row.find('div', class_='wcl-awayValue_Y-QR1') home_value = home_value_elem.get_text(strip=True) if home_value_elem else 'N/A' away_value = away_value_elem.get_text(strip=True) if away_value_elem else 'N/A' return { 'category': category, 'home_value': home_value, 'away_value': away_value } except Exception as e: print(f"Ошибка при парсинге строки статистики: {e}") return None def analyze_performance(self, match_info, stats_data): """Анализ игры команд и создание рейтинговой оценки""" # Извлекаем ключевые статистические данные key_stats = self._extract_key_stats(stats_data) home_team = match_info.get('home_team', 'Home') away_team = match_info.get('away_team', 'Away') # Пытаемся извлечь количество забитых голов из счета home_goals = 0 away_goals = 0 score = match_info.get('score', '') try: if score and '-' in score: parts = score.replace(' ', '').split('-') if len(parts) == 2: home_goals = int(parts[0]) away_goals = int(parts[1]) except ValueError: pass # Создаем оценку для каждой команды home_analysis = self._create_team_analysis( home_team, key_stats, is_home=True, goals=home_goals ) away_analysis = self._create_team_analysis( away_team, key_stats, is_home=False, goals=away_goals ) # Сравниваем команды comparison = self._compare_teams(home_analysis, away_analysis) return { 'home_team': home_analysis, 'away_team': away_analysis, 'comparison': comparison, 'overall_verdict': self._generate_overall_verdict(home_analysis, away_analysis, match_info), 'key_stats': key_stats } def _extract_key_stats(self, stats_data): """Извлечение ключевых статистических данных в числовом виде""" key_stats = {} # Собираем все статистики в один словарь по названию показателя all_stats = {} for section, stats in stats_data.items(): for stat in stats: all_stats[stat['category']] = { 'home': stat['home_value'], 'away': stat['away_value'] } # Извлекаем числовые значения из строк def parse_value(value_str): try: # Убираем проценты и скобки, берем первое число clean_value = value_str.split('%')[0].split('(')[0].strip() return float(clean_value.replace(',', '.')) except Exception: return 0.0 # Ключевые показатели для анализа (если есть на странице) key_metrics = [ 'Expected Goals (xG)', 'Ball Possession', 'Total shots', 'Shots on target', 'Shots off target', 'Blocked shots', 'Big Chances', 'Corners', 'Offsides', 'Passes', 'Attacks', 'Dangerous attacks', 'Tackles', 'Fouls', 'Yellow Cards', 'Red Cards', 'Goalkeeper Saves' ] for metric in key_metrics: if metric in all_stats: key_stats[metric] = { 'home': parse_value(all_stats[metric]['home']), 'away': parse_value(all_stats[metric]['away']) } return key_stats def _create_team_analysis(self, team_name, key_stats, is_home=True, goals=0): """Создание детального анализа для одной команды""" side = 'home' if is_home else 'away' opponent_side = 'away' if is_home else 'home' stats = key_stats # Расчет рейтингов (0-10) attack_rating = self._calculate_attack_rating(stats, side, goals) defense_rating = self._calculate_defense_rating(stats, side, opponent_side) control_rating = self._calculate_control_rating(stats, side) efficiency_rating = self._calculate_efficiency_rating(stats, side, goals) # Общий рейтинг (0-10) с весами по компонентам overall_rating = ( attack_rating * 0.30 + defense_rating * 0.30 + control_rating * 0.25 + efficiency_rating * 0.15 ) # Ключевые сильные и слабые стороны strengths = self._identify_strengths(stats, side, opponent_side) weaknesses = self._identify_weaknesses(stats, side, opponent_side) # Детальные выводы с учетом рейтингов key_insights = self._generate_insights( stats, side, opponent_side, { 'attack': attack_rating, 'defense': defense_rating, 'control': control_rating, 'efficiency': efficiency_rating }, goals ) return { 'team_name': team_name, 'ratings': { 'overall': round(overall_rating, 1), 'attack': round(attack_rating, 1), 'defense': round(defense_rating, 1), 'control': round(control_rating, 1), 'efficiency': round(efficiency_rating, 1) }, 'strengths': strengths, 'weaknesses': weaknesses, 'key_insights': key_insights } def _calculate_attack_rating(self, stats, side, goals=0): """Новая схема расчета рейтинга атаки (0-10)""" score = 0.0 xg = stats.get('Expected Goals (xG)', {}).get(side) shots_on_target = stats.get('Shots on target', {}).get(side) total_shots = stats.get('Total shots', {}).get(side) big_chances = stats.get('Big Chances', {}).get(side) # xG: до 3 xG -> до 6 баллов if isinstance(xg, (int, float)): score += min(max(xg, 0.0), 3.0) * 2.0 # Удары в створ: до 8 -> до 4 баллов if isinstance(shots_on_target, (int, float)): score += min(max(shots_on_target, 0.0), 8.0) * 0.5 # Большие моменты: до 5 -> до 3.5 баллов if isinstance(big_chances, (int, float)): score += min(max(big_chances, 0.0), 5.0) * 0.7 # Реальные голы как бонус за завершение атак: до 4 -> до 4.8 баллов if isinstance(goals, (int, float)): score += min(max(goals, 0.0), 4.0) * 1.2 # Нормируем на 0-10 rating = score / 1.8 # ~18 баллов -> 10 return max(0.0, min(rating, 10.0)) def _calculate_defense_rating(self, stats, side, opponent_side): """Расчет рейтинга защиты (0-10). Чем меньше моментов у соперника, тем выше рейтинг.""" score = 0.0 xg_against = stats.get('Expected Goals (xG)', {}).get(opponent_side) shots_on_target_against = stats.get('Shots on target', {}).get(opponent_side) big_chances_against = stats.get('Big Chances', {}).get(opponent_side) tackles = stats.get('Tackles', {}).get(side) fouls = stats.get('Fouls', {}).get(side) yellow_cards = stats.get('Yellow Cards', {}).get(side) # xG соперника: чем меньше, тем лучше (порог ~2.5) if isinstance(xg_against, (int, float)): score += max(0.0, (2.5 - xg_against)) * 3.0 # Удары соперника в створ: чем меньше, тем лучше if isinstance(shots_on_target_against, (int, float)): score += max(0.0, (7.0 - shots_on_target_against)) * 0.6 # Большие моменты соперника if isinstance(big_chances_against, (int, float)): score += max(0.0, (4.0 - big_chances_against)) * 0.8 # Отборы в плюс if isinstance(tackles, (int, float)): score += min(max(tackles, 0.0), 40.0) * 0.1 # Фолы и карточки в минус if isinstance(fouls, (int, float)): score -= min(max(fouls, 0.0), 20.0) * 0.1 if isinstance(yellow_cards, (int, float)): score -= min(max(yellow_cards, 0.0), 5.0) * 0.5 rating = max(0.0, min(score, 10.0)) return rating def _calculate_control_rating(self, stats, side): """Расчет рейтинга контроля игры (0-10)""" score = 0.0 possession = stats.get('Ball Possession', {}).get(side) passes = stats.get('Passes', {}).get(side) total_shots = stats.get('Total shots', {}).get(side) # Владение мячом: учитываем все, что выше 40% if isinstance(possession, (int, float)): score += max(0.0, possession - 40.0) * 0.3 # Передачи: предполагаем %, если значение > 100, режем до 100 if isinstance(passes, (int, float)): pass_metric = min(max(passes, 0.0), 100.0) score += max(0.0, pass_metric - 70.0) * 0.2 # Общее количество ударов как индикатор территориального контроля if isinstance(total_shots, (int, float)): score += min(max(total_shots, 0.0), 20.0) * 0.2 rating = max(0.0, min(score, 10.0)) return rating def _calculate_efficiency_rating(self, stats, side, goals=0): """Расчет рейтинга эффективности реализации (0-10)""" rating = 5.0 # базовая оценка shots_on_target = stats.get('Shots on target', {}).get(side) total_shots = stats.get('Total shots', {}).get(side) xg = stats.get('Expected Goals (xG)', {}).get(side) # Эффективность попаданий в створ if isinstance(shots_on_target, (int, float)) and isinstance(total_shots, (int, float)) and total_shots > 0: ratio = shots_on_target / total_shots * 100.0 rating += (ratio - 40.0) * 0.15 # 40% = средний уровень # Соотношение голов и xG: забили больше, чем ожидалось -> плюс if isinstance(xg, (int, float)) and xg > 0 and isinstance(goals, (int, float)): diff = goals - xg rating += diff * 1.0 # +/- 1 балл за каждый "перебор" xG return max(0.0, min(rating, 10.0)) def _identify_strengths(self, stats, side, opponent_side): """Определение сильных сторон команды""" strengths = [] xg = stats.get('Expected Goals (xG)', {}).get(side, 0.0) xg_against = stats.get('Expected Goals (xG)', {}).get(opponent_side, 0.0) shots_on_target = stats.get('Shots on target', {}).get(side, 0.0) total_shots = stats.get('Total shots', {}).get(side, 0.0) big_chances = stats.get('Big Chances', {}).get(side, 0.0) possession = stats.get('Ball Possession', {}).get(side, 0.0) passes = stats.get('Passes', {}).get(side, 0.0) tackles = stats.get('Tackles', {}).get(side, 0.0) tackles_opp = stats.get('Tackles', {}).get(opponent_side, 0.0) fouls = stats.get('Fouls', {}).get(side, 0.0) if xg >= 1.5: strengths.append("Создавали много опасных моментов (высокий xG)") if shots_on_target >= 6: strengths.append("Часто доводили атаки до ударов в створ") if big_chances >= 3: strengths.append("Создали большое количество голевых моментов") if possession >= 55: strengths.append("Доминировали во владении мячом") if passes >= 82: strengths.append("Высокая точность передач") if tackles >= 18 and tackles >= tackles_opp: strengths.append("Хорошо работали в отборе мяча") if xg_against <= 0.9: strengths.append("Почти не позволяли сопернику создавать моменты") if total_shots >= 14: strengths.append("Постоянно нагружали оборону соперника ударами") if fouls <= 10: strengths.append("Сохраняли дисциплину и не фолили лишний раз") return strengths def _identify_weaknesses(self, stats, side, opponent_side): """Определение слабых сторон команды""" weaknesses = [] xg = stats.get('Expected Goals (xG)', {}).get(side, 0.0) xg_against = stats.get('Expected Goals (xG)', {}).get(opponent_side, 0.0) shots_on_target = stats.get('Shots on target', {}).get(side, 0.0) total_shots = stats.get('Total shots', {}).get(side, 0.0) big_chances = stats.get('Big Chances', {}).get(side, 0.0) possession = stats.get('Ball Possession', {}).get(side, 0.0) passes = stats.get('Passes', {}).get(side, 0.0) tackles = stats.get('Tackles', {}).get(side, 0.0) fouls = stats.get('Fouls', {}).get(side, 0.0) yellow_cards = stats.get('Yellow Cards', {}).get(side, 0.0) if xg < 0.8: weaknesses.append("Слабо выглядели в атаке (низкий xG)") if shots_on_target <= 3 and total_shots <= 9: weaknesses.append("Практически не создавали опасных ударов") if big_chances <= 1: weaknesses.append("Редко доводили атаки до по-настоящему голевых моментов") if possession <= 40: weaknesses.append("Проиграли борьбу за мяч и инициативу") if passes and passes < 78: weaknesses.append("Невысокая точность передач, много потерь мяча") if tackles <= 10: weaknesses.append("Недостаточная плотность в единоборствах") if xg_against >= 1.8: weaknesses.append("Слишком много позволяли сопернику в своей штрафной") if fouls >= 15: weaknesses.append("Излишняя агрессия и фолы") if yellow_cards >= 4: weaknesses.append("Проблемы с дисциплиной (много желтых карточек)") return weaknesses def _generate_insights(self, stats, side, opponent_side, ratings, goals): """Генерация ключевых инсайтов по команде""" insights = [] attack_rating = ratings['attack'] defense_rating = ratings['defense'] control_rating = ratings['control'] efficiency_rating = ratings['efficiency'] xg = stats.get('Expected Goals (xG)', {}).get(side, 0.0) xg_against = stats.get('Expected Goals (xG)', {}).get(opponent_side, 0.0) shots_on_target = stats.get('Shots on target', {}).get(side, 0.0) total_shots = stats.get('Total shots', {}).get(side, 0.0) possession = stats.get('Ball Possession', {}).get(side, 0.0) # Блок по атаке if attack_rating >= 8: insights.append( f"Атакующая игра была одной из сильных сторон: xG = {xg:.2f}, " f"ударов в створ = {int(shots_on_target)}." ) elif attack_rating <= 4: insights.append( "В атаке команда выглядела бледно и редко доводила свои подходы до " "действительно опасных моментов." ) # Блок по обороне if defense_rating >= 8: insights.append( f"Оборона сработала надежно: допустили всего {xg_against:.2f} xG соперника." ) elif defense_rating <= 4: insights.append( "В защите команда часто теряла позиции и позволяла сопернику создавать " "слишком много моментов." ) # Блок по контролю игры if control_rating >= 7.5: insights.append( f"Команда хорошо контролировала ход матча (владение около {possession:.1f}% " f"и постоянное давление ударами, всего {int(total_shots)} ударов)." ) elif control_rating <= 4: insights.append( "Контроль матча в основном был у соперника: команда чаще оборонялась, " "а не диктовала условия игры." ) # Блок по эффективности if efficiency_rating >= 7.5: insights.append("Хорошо использовали свои моменты и достаточно эффективно распоряжались ударами.") elif efficiency_rating <= 4: insights.append("Реализация моментов подвела: от атакующих действий ожидалось большего.") # Дополнительный комментарий по голам if goals >= 3: insights.append("Большое количество забитых голов подтверждает высокий атакующий потенциал в этом матче.") elif goals == 0 and xg >= 1.0: insights.append( "При достойном качестве моментов забить так и не удалось — подвела заключительная стадия атак." ) return insights def _compare_teams(self, home_analysis, away_analysis): """Сравнение команд""" comparison = { 'dominance': '', 'key_advantages': [] } home_rating = home_analysis['ratings']['overall'] away_rating = away_analysis['ratings']['overall'] if home_rating - away_rating >= 2: comparison['dominance'] = f"{home_analysis['team_name']} доминировала в матче" elif away_rating - home_rating >= 2: comparison['dominance'] = f"{away_analysis['team_name']} доминировала в матче" else: comparison['dominance'] = "Балансированный матч" # Определение ключевых преимуществ if home_analysis['ratings']['attack'] > away_analysis['ratings']['attack'] + 1: comparison['key_advantages'].append(f"{home_analysis['team_name']} - более опасная атака") elif away_analysis['ratings']['attack'] > home_analysis['ratings']['attack'] + 1: comparison['key_advantages'].append(f"{away_analysis['team_name']} - более опасная атака") if home_analysis['ratings']['defense'] > away_analysis['ratings']['defense'] + 1: comparison['key_advantages'].append(f"{home_analysis['team_name']} - надежнее в защите") elif away_analysis['ratings']['defense'] > home_analysis['ratings']['defense'] + 1: comparison['key_advantages'].append(f"{away_analysis['team_name']} - надежнее в защите") if home_analysis['ratings']['control'] > away_analysis['ratings']['control'] + 1: comparison['key_advantages'].append(f"{home_analysis['team_name']} - лучше контролировала игру") elif away_analysis['ratings']['control'] > home_analysis['ratings']['control'] + 1: comparison['key_advantages'].append(f"{away_analysis['team_name']} - лучше контролировала игру") return comparison def _generate_overall_verdict(self, home_analysis, away_analysis, match_info): """Генерация общего вердикта по матчу""" home_rating = home_analysis['ratings']['overall'] away_rating = away_analysis['ratings']['overall'] verdicts = [ "Очень односторонний матч", "Явное доминирование одной команды", "Уравновешенная игра с небольшим преимуществом", "Близкая и напряженная борьба", "Ничья по игровому наполнению" ] rating_diff = abs(home_rating - away_rating) if rating_diff >= 3: verdict = verdicts[0] elif rating_diff >= 2: verdict = verdicts[1] elif rating_diff >= 1: verdict = verdicts[2] elif rating_diff >= 0.5: verdict = verdicts[3] else: verdict = verdicts[4] return { 'verdict': verdict, 'entertainment_rating': round((home_rating + away_rating) / 2, 1), 'recommendation': "Рекомендуем к просмотру" if (home_rating + away_rating) / 2 >= 6 else "Обычный матч" } def save_to_json(self, data, filename): """Сохранение данных в JSON файл""" with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) print(f"Данные сохранены в {filename}") def save_to_excel(self, data, filename): """Сохранение данных в Excel файл""" try: # Создаем DataFrame для статистики rows = [] for section, stats in data['statistics'].items(): for stat in stats: rows.append({ 'Section': section, 'Category': stat['category'], 'Home_Value': stat['home_value'], 'Away_Value': stat['away_value'] }) df_stats = pd.DataFrame(rows) # Создаем DataFrame для основной информации df_info = pd.DataFrame([data['match_info']]) # Создаем DataFrame для анализа analysis_data = [] for team in ['home_team', 'away_team']: team_data = data['analysis'][team] analysis_data.append({ 'Team': team_data['team_name'], 'Overall_Rating': team_data['ratings']['overall'], 'Attack_Rating': team_data['ratings']['attack'], 'Defense_Rating': team_data['ratings']['defense'], 'Control_Rating': team_data['ratings']['control'], 'Efficiency_Rating': team_data['ratings']['efficiency'], 'Strengths': ', '.join(team_data['strengths']), 'Weaknesses': ', '.join(team_data['weaknesses']), 'Key_Insights': ' | '.join(team_data['key_insights']) }) df_analysis = pd.DataFrame(analysis_data) # Сохраняем в Excel с несколькими листами with pd.ExcelWriter(filename, engine='openpyxl') as writer: df_info.to_excel(writer, sheet_name='Match_Info', index=False) df_stats.to_excel(writer, sheet_name='Statistics', index=False) df_analysis.to_excel(writer, sheet_name='Analysis', index=False) print(f"Данные сохранены в {filename}") except Exception as e: print(f"Ошибка при сохранении в Excel: {e}") def close(self): """Закрытие драйвера""" if self.driver: self.driver.quit() def print_analysis(self, data): """Красивый вывод анализа в консоль вместе с подробной статистикой""" analysis = data['analysis'] match_info = data['match_info'] stats = data.get('statistics', {}) print("\n" + "=" * 80) print("🏆 ДЕТАЛЬНЫЙ АНАЛИЗ МАТЧА") print("=" * 80) print(f"\n📊 МАТЧ: {match_info['home_team']} {match_info['score']} {match_info['away_team']}") print(f"📅 {match_info['date_time']} | {match_info['tournament']}") # Анализ домашней команды home = analysis['home_team'] print(f"\n{'=' * 40}") print(f"🏠 {home['team_name']} - ОЦЕНКА: {home['ratings']['overall']}/10") print(f"{'=' * 40}") print(f"⚽ Атака: {home['ratings']['attack']}/10 | 🛡️ Защита: {home['ratings']['defense']}/10") print(f"🎯 Контроль: {home['ratings']['control']}/10 | 📈 Эффективность: {home['ratings']['efficiency']}/10") if home['strengths']: print(f"\n✅ СИЛЬНЫЕ СТОРОНЫ:") for strength in home['strengths']: print(f" • {strength}") if home['weaknesses']: print(f"\n❌ СЛАБЫЕ СТОРОНЫ:") for weakness in home['weaknesses']: print(f" • {weakness}") if home['key_insights']: print(f"\n🔍 КЛЮЧЕВЫЕ ВЫВОДЫ:") for insight in home['key_insights']: print(f" • {insight}") # Анализ гостевой команды away = analysis['away_team'] print(f"\n{'=' * 40}") print(f"✈️ {away['team_name']} - ОЦЕНКА: {away['ratings']['overall']}/10") print(f"{'=' * 40}") print(f"⚽ Атака: {away['ratings']['attack']}/10 | 🛡️ Защита: {away['ratings']['defense']}/10") print(f"🎯 Контроль: {away['ratings']['control']}/10 | 📈 Эффективность: {away['ratings']['efficiency']}/10") if away['strengths']: print(f"\n✅ СИЛЬНЫЕ СТОРОНЫ:") for strength in away['strengths']: print(f" • {strength}") if away['weaknesses']: print(f"\n❌ СЛАБЫЕ СТОРОНЫ:") for weakness in away['weaknesses']: print(f" • {weakness}") if away['key_insights']: print(f"\n🔍 КЛЮЧЕВЫЕ ВЫВОДЫ:") for insight in away['key_insights']: print(f" • {insight}") # Сравнение и общий вердикт comparison = analysis['comparison'] verdict = analysis['overall_verdict'] print(f"\n{'=' * 40}") print("📊 СРАВНИТЕЛЬНЫЙ АНАЛИЗ") print(f"{'=' * 40}") print(f"📈 {comparison['dominance']}") if comparison['key_advantages']: print(f"\n🎯 КЛЮЧЕВЫЕ ПРЕИМУЩЕСТВА:") for advantage in comparison['key_advantages']: print(f" • {advantage}") print(f"\n{'=' * 40}") print("🎭 ОБЩИЙ ВЕРДИКТ") print(f"{'=' * 40}") print(f"📋 {verdict['verdict']}") print(f"⭐ Рейтинг зрелищности: {verdict['entertainment_rating']}/10") print(f"💡 {verdict['recommendation']}") # Подробная статистика print(f"\n{'=' * 40}") print("📊 ПОДРОБНАЯ СТАТИСТИКА МАТЧА") print(f"{'=' * 40}") if not stats: print("Статистика недоступна.") return for section, items in stats.items(): print(f"\n📂 {section}") for item in items: print(f" • {item['category']}: {item['home_value']} - {item['away_value']}")
if name == "main":
# URL матча
match_url = "https://www.flashscore.com/match/football/almeria-nF2Vy2D0/huesca-UytPaaEP/summary/stats/0/?mid=vuHxnkYh"
text# Создаем парсер parser = FlashscoreParser(headless=False) # headless=False для отладки try: # Парсим данные match_data = parser.parse_match_stats(match_url) if match_data: # Выводим красивый анализ parser.print_analysis(match_data) # Сохраняем в файлы parser.save_to_json(match_data, 'match_stats.json') parser.save_to_excel(match_data, 'match_stats.xlsx') except Exception as e: print(f"Ошибка: {e}") finally: parser.close() вот этот код работает только по определенной ссылке, то есть ссылку нужно ввести в код, но таких ссылок много так как много футбольных игр, поэтому я хочу дать одну ссылку на сайт https://www.flashscore.com/football/spain/laliga2/results/ на этом сайте код должен сам найти игры, они под селектором: <a href="https://www.flashscore.com/match/football/malaga-25tIqYiJ/valladolid-zkpajjvm/?mid=8vXzepHN" target="_self" class="eventRowLink" aria-describedby="g_1_8vXzepHN" title="Click for match detail!"></a> это одна из игр, таких игр там много, и например если я в коде напишу количество игр=10 то код должен проанализировать 10 таких игр, после того как код заходит в игру допустим по селектору <a href="https://www.flashscore.com/match/football/malaga-25tIqYiJ/valladolid-zkpajjvm/?mid=8vXzepHN" target="_self" class="eventRowLink" aria-describedby="g_1_8vXzepHN" title="Click for match detail!"></a> дальше он должен зайти во вкладку stats чтобы найти данные по статистике: <a data-analytics-element="SCN_TAB" data-analytics-alias="match-statistics" class="" href="/match/football/malaga-25tIqYiJ/valladolid-zkpajjvm/summary/stats/?mid=8vXzepHN" data-discover="true"><button data-testid="wcl-tab" data-selected="false" class="wcl-tab_GS7ig" role="tab">Stats</button></a> ну и он там найдет данные, после того как код проанализировал определенное количество игр он должен еще дополнительно по всем проанализорованным играм составить рейтинг команд начиная с самой лучшей, ну и естественно должен быть отдельно список проанализированных игр с итоговыми баллами по командам, еще обязательно должен быть список где будет отображено какая команда начинает играть лучше чем раньше или наоборот хуже, все это должно быть красиво расписано в excel файле красиво по столбиком или еще как-то чтобы было удобно читать и анализировать
Сейчас твой класс умеет разбирать один матч по заранее заданному stats-URL.
Давай превратим его в такого монстра:
a.eventRowLink);parse_match_stats;Тебе вообще не надо ломать существующую логику — мы просто добавим пару методов в класс и перепишем if __name__ == "__main__":.
Добавь в класс FlashscoreParser (внутрь класса, с таким же отступом как остальные методы):
pythondef _build_stats_url(self, match_url): """Построение URL вкладки Stats по URL матча""" from urllib.parse import urlsplit, urlunsplit # Если нам уже дали ссылку на статистику - просто возвращаем if '/summary/stats' in match_url: return match_url split = urlsplit(match_url) path = split.path.rstrip('/') # Удалим возможный '/summary' в конце (на всякий случай) if path.endswith('/summary'): path = path[: -len('/summary')] # Добавляем путь к статистике path = path.rstrip('/') + '/summary/stats/' return urlunsplit((split.scheme, split.netloc, path, split.query, ''))
Он берёт, например:
texthttps://www.flashscore.com/match/football/malaga-25tIqYiJ/valladolid-zkpajjvm/?mid=8vXzepHN
и превращает в:
texthttps://www.flashscore.com/match/football/malaga-25tIqYiJ/valladolid-zkpajjvm/summary/stats/?mid=8vXzepHN
который отлично подходит для твоего parse_match_stats.
Снова внутрь класса добавь:
pythondef parse_matches_from_results(self, results_url, max_matches=10): """ Парсинг нескольких матчей с страницы результатов лиги. Берём max_matches матчей со страницы results, для каждого строим URL вкладки Stats. """ try: print(f"Открываем страницу результатов: {results_url}") self.driver.get(results_url) # Ждём, пока появятся ссылки на матчи WebDriverWait(self.driver, 20).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'a.eventRowLink')) ) # Немного подождём, чтобы динамика дорисовалась time.sleep(3) match_elements = self.driver.find_elements(By.CSS_SELECTOR, 'a.eventRowLink') match_links = [] seen = set() for el in match_elements: href = el.get_attribute('href') if not href: continue if href in seen: continue if '/match/' not in href: continue seen.add(href) match_links.append(href) if len(match_links) >= max_matches: break print(f"Найдено матчей для анализа: {len(match_links)}") all_data = [] for i, match_url in enumerate(match_links, start=1): print(f"\n{'-' * 60}") print(f"Матч {i}/{len(match_links)}: {match_url}") stats_url = self._build_stats_url(match_url) print(f"URL статистики: {stats_url}") data = self.parse_match_stats(stats_url) if data: # сохраняем оригинальный URL матча (не только /summary/stats) data['match_url'] = match_url all_data.append(data) else: print("⚠️ Не удалось распарсить этот матч") return all_data except Exception as e: print(f"Ошибка при парсинге списка матчей: {e}") return []
Тут используется твой существующий parse_match_stats, только уже не по одному URL, а по списку матчей, взятых с results-страницы.
Теперь добавим метод, который из списка матчей (matches_data, то, что вернул parse_matches_from_results) соберёт один большой Excel.
Тоже внутрь класса:
pythondef save_multiple_matches_to_excel(self, matches_data, filename): """ Сохранение агрегированных данных по нескольким матчам: - общий список матчей с итоговыми оценками команд - рейтинг команд (средние оценки) - тренды по командам (улучшаются / ухудшаются) - подробная статистика по матчам """ if not matches_data: print("Нет данных для сохранения.") return matches_rows = [] team_rows = [] stats_rows = [] for idx, data in enumerate(matches_data, start=1): mi = data['match_info'] analysis = data['analysis'] home = analysis['home_team'] away = analysis['away_team'] verdict = analysis.get('overall_verdict', {}) # -------- лист Matches_Overview: по матчу в строке -------- matches_rows.append({ 'Match_Index': idx, 'DateTime': mi.get('date_time'), 'Tournament': mi.get('tournament'), 'Home_Team': mi.get('home_team'), 'Away_Team': mi.get('away_team'), 'Score': mi.get('score'), 'Status': mi.get('status'), 'Home_Overall': home['ratings']['overall'], 'Away_Overall': away['ratings']['overall'], 'Entertainment_Rating': verdict.get('entertainment_rating'), 'Verdict': verdict.get('verdict'), 'Recommendation': verdict.get('recommendation'), 'Match_URL': data.get('match_url'), }) # -------- лист Team_Matches: каждая команда в матче отдельной строкой -------- for team_key, is_home in (('home_team', True), ('away_team', False)): team_data = analysis[team_key] opponent_name = mi.get('away_team') if is_home else mi.get('home_team') team_rows.append({ 'Match_Index': idx, 'DateTime': mi.get('date_time'), 'Tournament': mi.get('tournament'), 'Team': team_data['team_name'], 'Side': 'Home' if is_home else 'Away', 'Opponent': opponent_name, 'Score': mi.get('score'), 'Status': mi.get('status'), 'Overall_Rating': team_data['ratings']['overall'], 'Attack_Rating': team_data['ratings']['attack'], 'Defense_Rating': team_data['ratings']['defense'], 'Control_Rating': team_data['ratings']['control'], 'Efficiency_Rating': team_data['ratings']['efficiency'], 'Strengths': ', '.join(team_data['strengths']), 'Weaknesses': ', '.join(team_data['weaknesses']), 'Key_Insights': ' | '.join(team_data['key_insights']), 'Match_URL': data.get('match_url'), }) # -------- лист Raw_Statistics: сырая статистика по всем матчам -------- for section, stats in data.get('statistics', {}).items(): for s in stats: stats_rows.append({ 'Match_Index': idx, 'DateTime': mi.get('date_time'), 'Home_Team': mi.get('home_team'), 'Away_Team': mi.get('away_team'), 'Section': section, 'Category': s['category'], 'Home_Value': s['home_value'], 'Away_Value': s['away_value'], }) # DataFrames df_matches = pd.DataFrame(matches_rows) df_team_matches = pd.DataFrame(team_rows) df_stats = pd.DataFrame(stats_rows) # Разбор даты для трендов if not df_team_matches.empty: df_team_matches['ParsedDate'] = pd.to_datetime( df_team_matches['DateTime'], errors='coerce', dayfirst=True ) # -------- лист Team_Ranking: средние оценки по командам -------- if not df_team_matches.empty: grouping = df_team_matches.groupby('Team') df_ranking = grouping.agg({ 'Overall_Rating': ['mean', 'count'], 'Attack_Rating': 'mean', 'Defense_Rating': 'mean', 'Control_Rating': 'mean', 'Efficiency_Rating': 'mean' }).reset_index() df_ranking.columns = [ 'Team', 'Avg_Overall_Rating', 'Matches_Played', 'Avg_Attack_Rating', 'Avg_Defense_Rating', 'Avg_Control_Rating', 'Avg_Efficiency_Rating' ] df_ranking = df_ranking.sort_values( by='Avg_Overall_Rating', ascending=False ) else: df_ranking = pd.DataFrame() # -------- лист Team_Trends: кто стал играть лучше/хуже -------- trend_rows = [] if not df_team_matches.empty: for team, df_team in df_team_matches.groupby('Team'): df_team = df_team.sort_values(['ParsedDate', 'Match_Index']) n = len(df_team) y = df_team['Overall_Rating'].tolist() if n == 0: continue if n < 3: trend = 'Недостаточно матчей для оценки тренда' start_rating = y[0] last_rating = y[-1] change = last_rating - start_rating else: # простая линейная регрессия по номеру матча x = list(range(n)) x_mean = sum(x) / n y_mean = sum(y) / n numerator = sum((xi - x_mean) * (yi - y_mean) for xi, yi in zip(x, y)) denominator = sum((xi - x_mean) ** 2 for xi in x) slope = numerator / denominator if denominator else 0.0 start_rating = y[0] last_rating = y[-1] change = last_rating - start_rating if slope >= 0.15: trend = 'Улучшение (команда играет лучше, чем раньше)' elif slope <= -0.15: trend = 'Ухудшение (команда играет хуже, чем раньше)' else: trend = 'Без выраженного тренда (примерно стабильно)' trend_rows.append({ 'Team': team, 'Trend': trend, 'Matches_Analyzed': n, 'First_Match_Rating': round(start_rating, 2), 'Last_Match_Rating': round(last_rating, 2), 'Change': round(change, 2) }) df_trends = pd.DataFrame(trend_rows) # -------- запись в Excel -------- try: with pd.ExcelWriter(filename, engine='openpyxl') as writer: df_matches.to_excel(writer, sheet_name='Matches_Overview', index=False) df_team_matches.to_excel(writer, sheet_name='Team_Matches', index=False) df_ranking.to_excel(writer, sheet_name='Team_Ranking', index=False) df_trends.to_excel(writer, sheet_name='Team_Trends', index=False) df_stats.to_excel(writer, sheet_name='Raw_Statistics', index=False) print(f"Агрегированные данные сохранены в {filename}") except Exception as e: print(f"Ошибка при сохранении в Excel: {e}")
Что будет в Excel:
Matches_Overview – список матчей (дата, турнир, пары, счёт, общий рейтинг по каждой команде, зрелищность, рекомендация).Team_Matches – построчно все выступления команд: в каком матче, против кого, home/away, все рейтинги + текстовые выводы.Team_Ranking – рейтинг команд по среднему Avg_Overall_Rating (и средние по атаке/защите/контролю/эффективности).Team_Trends – та самая табличка «кто начал играть лучше/хуже», с первым и последним рейтингом и разницей.Raw_Statistics – полная статистика по каждому показателю из вкладки Stats.main: парсим N матчей из LaLiga2 и сохраняем ExcelСамый конец файла перепиши так:
pythonif __name__ == "__main__": # Страница результатов лиги results_url = "https://www.flashscore.com/football/spain/laliga2/results/" max_matches = 10 # сколько матчей анализировать parser = FlashscoreParser(headless=False) # headless=False для отладки try: # Парсим несколько матчей matches_data = parser.parse_matches_from_results(results_url, max_matches=max_matches) # Если хочешь, можешь сразу выводить в консоль детальный анализ по каждому матчу: # for data in matches_data: # parser.print_analysis(data) # Сохраняем агрегированные данные по всем матчам if matches_data: parser.save_multiple_matches_to_excel(matches_data, 'laliga2_matches_analysis.xlsx') except Exception as e: print(f"Ошибка: {e}") finally: parser.close()
Если хочешь, дальше можно:
0.15 в методе трендов), чтобы сделать критерий улучшения/ухудшения более строгим или мягким.Если с интеграцией этих методов в твой файл что-то не соберётся (ошибка/трейсбек) — скинь текст ошибки, я помогу добить до рабочего состояния.