import time import json from selenium import webdr...

बनाया गया: 30 नवंबर 2025

का उपयोग करके उत्तर दिया गया GPT-5.1 Thinking द्वारा 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)

text
def 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 файле красиво по столбиком или еще как-то чтобы было удобно читать и анализировать

विचार

Crafting succinct response while managing code size

The syntax is fine, but since Selenium isn't available here, I’ve only tested the syntax. I shouldn't mention running the code. I can say "я проверил, синтаксис ок" without referencing code tools. The full script is too large to send, so I’ll focus on key parts to avoid overwhelming the response limit.

Providing necessary code modifications

We shouldn't send the entire code; I'll just share the key changes. The modifications will include the new private method _build_stats_url and the methods parse_matches_from_results and save_multiple_matches_to_excel. The rest of the code remains unchanged. I'll give the user the instruction in Russian: "оставляем всё как есть, просто добавляем вот такие методы и переписываем main."

Providing final answer with steps and code modifications

First, I’ll explain in Russian: "Смотри, тебе нужно научить парсер сам забирать список матчей с results-страницы, проходить по ним, строить stats-URL, а потом уже использовать твою существующую логику анализа." Then, I’ll include sections with the new methods and updated main, plus a small docstring adjustment for parse_match_stats.

Clarifying code structure and Excel columns

I'll suggest optionally calling print_analysis for each game. Next, I’ll explain the columns in the Excel output, including sheet names and the logic for ranking and trend detection. For the code snippets, I'll emphasize placing them inside the class with proper indentation to avoid syntax issues.

Providing minimal modifications with code

We don't need to modify parse_match_stats as it still works fine. The important changes are in three methods: _build_stats_url, parse_matches_from_results, and save_multiple_matches_to_excel. I'll show only these methods, ensuring necessary imports like urlsplit and urlunsplit are handled inside the function. The final code will maintain compatibility with the FlashscoreParser.

Preparing final code segments

I’m going to copy the necessary methods manually from the original code: _build_stats_url, parse_matches_from_results, and save_multiple_matches_to_excel. Then, I’ll include the updated main block outside the class, formatted with 4 spaces of indentation. I’ll ensure these segments are properly restructured for the final answer.

Copying the functions and ensuring formatting

I'll check each function to make sure the formatting uses 4 spaces. I've confirmed the code for _build_stats_url, and parse_matches_from_results looks good for copying. Next, I’ll copy the save_multiple_matches_to_excel function, making sure not to include unnecessary references like pd/openpyxl.

Then, there's the trending method to copy too.

Ensuring correct formatting and copying functions

I'll ensure

Thought for 4m 50s

उत्तर

Сейчас твой класс умеет разбирать один матч по заранее заданному stats-URL.
Давай превратим его в такого монстра:

  • берёт results-страницу (например, LaLiga2);
  • сам находит первые N матчей (a.eventRowLink);
  • для каждого строит URL вкладки Stats и запускает твой же parse_match_stats;
  • после всех матчей:
    • делает рейтинг команд (по среднему overall);
    • считает тренд по каждой команде (улучшение / ухудшение / стабильно);
    • сохраняет всё в красивый Excel с несколькими листами.

Тебе вообще не надо ломать существующую логику — мы просто добавим пару методов в класс и перепишем if __name__ == "__main__":.


1. Метод, который из URL матча строит URL вкладки Stats

Добавь в класс FlashscoreParser (внутрь класса, с таким же отступом как остальные методы):

python
def _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, ''))

Он берёт, например:

text
https://www.flashscore.com/match/football/malaga-25tIqYiJ/valladolid-zkpajjvm/?mid=8vXzepHN

и превращает в:

text
https://www.flashscore.com/match/football/malaga-25tIqYiJ/valladolid-zkpajjvm/summary/stats/?mid=8vXzepHN

который отлично подходит для твоего parse_match_stats.


2. Метод, который собирает N матчей с results-страницы и парсит каждый

Снова внутрь класса добавь:

python
def 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-страницы.


3. Агрегированный Excel: рейтинг, тренды, список матчей, статистика

Теперь добавим метод, который из списка матчей (matches_data, то, что вернул parse_matches_from_results) соберёт один большой Excel.

Тоже внутрь класса:

python
def 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.

4. Новый main: парсим N матчей из LaLiga2 и сохраняем Excel

Самый конец файла перепиши так:

python
if __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()

Если хочешь, дальше можно:

  • добавить фильтрацию по дате (например, только последние туры);
  • сделать отдельные Excel-файлы по лигам/странам;
  • менять порог тренда (0.15 в методе трендов), чтобы сделать критерий улучшения/ухудшения более строгим или мягким.

Если с интеграцией этих методов в твой файл что-то не соберётся (ошибка/трейсбек) — скинь текст ошибки, я помогу добить до рабочего состояния.

इस प्रश्नोत्तर को साझा करें