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 0000000..c395252 Binary files /dev/null and b/fantacalcio/Quotazioni_Fantacalcio_26_27.xlsx differ 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 ──────────────────────────────────────────