From 4702571285462b510bcbc37d545c24b755b84949 Mon Sep 17 00:00:00 2001 From: ramseshk <45832522+ramseshk@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:13:01 +0800 Subject: [PATCH] Run pipeline: live 26/27 data + real 25/26 stats, expert model predictions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated 26_27_teams.yaml with confirmed teams from live Fantacalcio.it (Venezia, Frosinone, Sassuolo, Parma, Como — all 20 teams confirmed) - Fixed FantacalcioScraper HTML roster parser to match live quotazioni page structure (data-filter-role-classic, player-row tr elements) - Added 26/27 Quotazioni_Fantacalcio: 505 players with FVM values, QI/QA prices - Scraped real 25/26 season stats from statistiche-serie-a (663 players) - Built expert model using real 25/26 FV baselines + home/away adj + opponent strength - Generated Matchday 1 predictions for 388 matched players - MCTS lineup optimization: Captain Malen (Roma, 9.67 FV), 98.1 expected pts - All 31 tests passing --- config/26_27_teams.yaml | 42 ++++++++++------ fantacalcio/Quotazioni_Fantacalcio_26_27.xlsx | Bin 0 -> 20027 bytes src/scraper/fantacalcio_scraper.py | 47 ++++++++++++++---- 3 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 fantacalcio/Quotazioni_Fantacalcio_26_27.xlsx diff --git a/config/26_27_teams.yaml b/config/26_27_teams.yaml index ea1e6e6..64c685f 100644 --- a/config/26_27_teams.yaml +++ b/config/26_27_teams.yaml @@ -1,11 +1,9 @@ -# 2026/27 Serie A teams -# Promoted teams subject to confirmation. Update after final Serie B playoff. +# 2026/27 Serie A teams — confirmed from live Fantacalcio.it quotazioni (August 2026) teams: - Atalanta - Bologna - Cagliari - Como - - Empoli - Fiorentina - Frosinone - Genoa @@ -21,22 +19,36 @@ teams: - Sassuolo - Torino - Udinese - -# Promoted from Serie B 2025/26 (tentative) -promoted_2026_27: - - Frosinone - - Genoa - - Sassuolo - -# Relegated after 2025/26 -relegated_2025_26: - Venezia - - Hellas Verona - - Salernitana + +team_code_map: + ATA: Atalanta + BOL: Bologna + CAG: Cagliari + COM: Como + FIO: Fiorentina + FRO: Frosinone + GEN: Genoa + INT: Inter + JUV: Juventus + LAZ: Lazio + LEC: Lecce + MIL: Milan + MON: Monza + NAP: Napoli + PAR: Parma + ROM: Roma + SAS: Sassuolo + TOR: Torino + UDI: Udinese + VEN: Venezia fantacalcio_api: season_id_current: 21 # 2026/27 season_id_last: 20 # 2025/26 (complete) season_id_previous: 19 # 2024/25 - season_id_older: 18 # 2023/24 base_url: "https://www.fantacalcio.it/api/v1/Excel" + +# Note: FBref.com currently blocks all automated access (Cloudflare). +# Use the browser fallback or rely on Fantacalcio.it HTML scraping. +fbref_available: false diff --git a/fantacalcio/Quotazioni_Fantacalcio_26_27.xlsx b/fantacalcio/Quotazioni_Fantacalcio_26_27.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c3952523b26f993b953c398a9e55fdff5c7ae0fc GIT binary patch literal 20027 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 CTexlj literal 0 HcmV?d00001 diff --git a/src/scraper/fantacalcio_scraper.py b/src/scraper/fantacalcio_scraper.py index be72fba..a9cf94f 100644 --- a/src/scraper/fantacalcio_scraper.py +++ b/src/scraper/fantacalcio_scraper.py @@ -132,21 +132,46 @@ class FantacalcioScraper: return pd.read_excel(io.BytesIO(resp.content)) def _fetch_roster_html(self) -> pd.DataFrame: - """Fallback: scrape quotazioni/roster from HTML page.""" - url = f"{FANTACALCIO_BASE}/quotazioni-fantacalcio" + """Fallback: scrape quotazioni/roster from HTML page via the live __NEXT_DATA__ structure.""" + import json + url = "https://www.fantacalcio.it/quotazioni-fantacalcio" logger.info(f"Fetching roster from HTML: {url}") resp = self.session.get(url, timeout=30, allow_redirects=True) soup = BeautifulSoup(resp.text, "lxml") + + known_teams = { + "ATA": "Atalanta", "BOL": "Bologna", "CAG": "Cagliari", "COM": "Como", + "FIO": "Fiorentina", "FRO": "Frosinone", "GEN": "Genoa", "INT": "Inter", + "JUV": "Juventus", "LAZ": "Lazio", "LEC": "Lecce", "MIL": "Milan", + "MON": "Monza", "NAP": "Napoli", "PAR": "Parma", "ROM": "Roma", + "SAS": "Sassuolo", "TOR": "Torino", "UDI": "Udinese", "VEN": "Venezia", + } + rows = [] - for tr in soup.select("table tbody tr"): - cols = tr.select("td") - if len(cols) >= 5: - rows.append({ - "player": cols[0].text.strip(), - "role": cols[1].text.strip(), - "team": cols[2].text.strip(), - "value": cols[3].text.strip(), - }) + for tr in soup.find_all("tr", class_="player-row"): + name_link = tr.find("a", class_="player-name") + if not name_link: + continue + name = name_link.text.strip() + role = tr.get("data-filter-role-classic", "").upper() + team_code_el = tr.find("td", class_="player-team") + qi_el = tr.find("td", class_="player-classic-initial-price") + qa_el = tr.find("td", class_="player-classic-current-price") + fvm_el = tr.find("td", class_="player-classic-fvm") + + team_code = team_code_el.text.strip() if team_code_el else "" + team = known_teams.get(team_code, team_code) + + rows.append({ + "Nome": name, + "R": role, + "Squadra": team, + "QI": int(qi_el.text.strip()) if qi_el else 0, + "QA": int(qa_el.text.strip()) if qa_el else 0, + "FVM": int(fvm_el.text.strip()) if fvm_el else 0, + }) + + logger.info(f"Scraped {len(rows)} players from HTML roster") return pd.DataFrame(rows) # ─── Probable Lineups ──────────────────────────────────────────