Files
lewangoalski/FlaskWebProject/FlaskWebProject/fs_scraper.py
Pc b5fdbb3230 Updating matches
Updating players stats(not finished)
Not tested
2025-06-04 19:50:18 +02:00

222 lines
9.3 KiB
Python

from lewy_db import baza as ldb
from lewy_globals import colors as c
import json
import lewy_globals
import requests
import time
from sqlalchemy import func
def safe_traverse(obj: dict, path: list, default=None):
result = obj
try:
for x in path:
result = result[x]
except KeyError:
result = default
# print(f"error reading: {' -> '.join(path)} - returning: {default}")
finally:
return result
def get_stat_value(stats: dict, stat_id: str, field: str = "value", default=None):
"""
Bezpiecznie pobiera wartość z pola 'value' lub 'type' dla danego stat_id w strukturze 'stats'.
:param stats: słownik ze statystykami
:param stat_id: ID statystyki jako string, np. "595"
:param field: 'value' lub 'type'
:param default: wartość domyślna zwracana, jeśli coś pójdzie nie tak
:return: wartość z pola lub default
"""
try:
return stats[stat_id][field]
except (KeyError, TypeError):
return default
class scraper:
headers = {
'x-fsign': 'SW9D1eZo'
}
db = None
def __init__(self):
self.db = lewy_globals.getDb()
pass
def pobierzDaneNajlepszegoSportowcaNaSwiecie(self) -> dict:
response = requests.get('https://3.flashscore.ninja/3/x/feed/plm_MVC8zHZD_0', headers=headers)
return json.loads(response.text)
def pobierz_pojedyncza_strone(self, zewnetrzne_id_sportowca: str = "MVC8zHZD", nr_strony: int = 0) -> dict:
if len(zewnetrzne_id_sportowca) != 8:
raise ValueError("Zewnętrzne ID sportowca powinno być długości 8!")
response = requests.get(f'https://3.flashscore.ninja/3/x/feed/plm_{zewnetrzne_id_sportowca}_{nr_strony}', headers=self.headers)
return json.loads(response.text)
def __czy_x_istnieje(self, typ, **id):
rekord = self.db.simple_select_all(typ, **id)
if rekord is not None and rekord != []:
return True
else:
return False
def czy_mecz_istnieje(self, zewnetrzne_id_meczu: str):
# mecz = db.simple_select_all(ldb.mecze, zewnetrzne_id_meczu=zewnetrzne_id_meczu)
# if mecz is not None and mecz != []:
# return True
# else:
# return False
return self.__czy_x_istnieje("mecze", zewnetrzne_id_meczu=zewnetrzne_id_meczu)
def czy_klub_istnieje(self, id_klubu: str):
# mecz = db.simple_select_all(ldb.mecze, zewnetrzne_id_meczu=zewnetrzne_id_meczu)
# if mecz is not None and mecz != []:
# return True
# else:
# return False
return self.__czy_x_istnieje("kluby", id_klubu=id_klubu)
def id_na_imie_nazwisko_urodziny(self, zewnetrzne_id_sportowca: str = "MVC8zHZD"):
"""
Scraper z dykty xD
Pobiera imiona, nazwiska i dni urodzin sportowców z zewnętrznego id.
Działa na słowo honoru.
:param zewnetrzne_id_sportowca: Zewnętrzne id sportowca
:type zewnetrzne_id_sportowca: str
"""
if len(zewnetrzne_id_sportowca) != 8:
raise ValueError("Zewnętrzne ID sportowca powinno być długości 8!")
r = requests.get(f'https://www.flashscore.pl/?r=4:{zewnetrzne_id_sportowca}')
page = r.text
name_start_pos = page.find("data-testid=\"wcl-scores-heading-02\">") + 36
name_end_pos = page.find("</", name_start_pos)
name = page[name_start_pos:name_end_pos].strip().split(' ')
# Tak wiem... można by było użyć beautifulsoup4, ale nie ma sensu dodawać nowych zależności dla tylko jednej metody.
birthday_start_pos_1 = page.find("data-testid=\"wcl-scores-simpleText-01\">", name_end_pos) + 39
birthday_start_pos_2 = page.find("data-testid=\"wcl-scores-simpleText-01\">", birthday_start_pos_1) + 39
birthday_start_pos_3 = page.find("data-testid=\"wcl-scores-simpleText-01\">", birthday_start_pos_2) + 39
birthday_start_pos = page.find("data-testid=\"wcl-scores-simpleText-01\">", birthday_start_pos_3) + 39
birthday_end_pos = page.find("</", birthday_start_pos) - 1
birthday = None if birthday_end_pos - birthday_start_pos > 20 else page[birthday_start_pos:birthday_end_pos].strip(" ()")
return name, birthday
def aktualizuj_dane_sportowca(self, zewnetrzne_id_sportowca: str = "MVC8zHZD"):
stop_scraping = False
matches_to_add = []
# TODO: Sprawdź, czy sportowiec istnieje w bazie.
# Jeśli nie, dodaj go w podobny sposób, jak
# w sample_data_init() (w lewy_db.py).
page = 0
match_num = 0
while not stop_scraping:
retrieved_page = self.pobierz_pojedyncza_strone(zewnetrzne_id_sportowca=zewnetrzne_id_sportowca, nr_strony=page)
if not safe_traverse(retrieved_page, ["hasMoreLastMatches"], default=False):
stop_scraping = True
break
print(f"{c.WARNING}Pobrano nową stronę {page}{c.ENDC}:\n{retrieved_page}")
retrieved_matches = safe_traverse(retrieved_page, ["lastMatches"], default=[])
for match in retrieved_matches:
match_id = safe_traverse(match, ["eventEncodedId"], default="non-existent-match-id")
home_club_id = safe_traverse(match, ["homeParticipantUrl"], default="non-existent-club-id")
away_club_id = safe_traverse(match, ["awayParticipantUrl"], default="non-existent-club-id")
# Sprawdź, czy mecz nie znajduje się już w bazie
if self.czy_mecz_istnieje(zewnetrzne_id_meczu=match_id):
stop_scraping = True
break
# Sprawdź, czy klub znajduje się już w bazie. Jeśli nie,
# trzeba go dodać przed meczem.
if not self.czy_klub_istnieje(id_klubu=home_club_id):
print(f"{c.OKCYAN}Nowy klub{c.ENDC}: {home_club_id}")
self.db.simple_insert_one("kluby",
id_klubu=home_club_id,
pelna_nazwa=safe_traverse(match, ["homeParticipantName"]),
skrocona_nazwa=safe_traverse(match, ["homeParticipant3CharName"]))
if not self.czy_klub_istnieje(id_klubu=away_club_id):
print(f"{c.OKCYAN}Nowy klub{c.ENDC}: {away_club_id}")
self.db.simple_insert_one("kluby",
id_klubu=away_club_id,
pelna_nazwa=safe_traverse(match, ["awayParticipantName"]),
skrocona_nazwa=safe_traverse(match, ["awayParticipant3CharName"]))
# TODO: (opcjonalnie) zamień *słownik match* na *obiekt mecz*
# TODO: dodaj obiekt mecz do bazy (simple_insert_one(), simple_insert_many())
print(f"{c.OKCYAN}Nowy mecz ({match_num}){c.ENDC}: {match}")
self.db.simple_insert_one("mecze",
zewnetrzne_id_meczu= safe_traverse(match, ["eventId"], default=""),
data= safe_traverse(match, ["eventStartTime"], default=func.now()),
gospodarze_id= safe_traverse(match, ["homeParticipant3CharName"], default=0),
gospodarze= safe_traverse(match, ["homeParticipantName"], default=""),
goscie_id= safe_traverse(match, ["awayParticipant3CharName"], default=0),
goscie= safe_traverse(match, ["awayParticipantName"], default=""),
gosp_wynik= safe_traverse(match, ["homeScore"], default=0),
gosc_wynik= safe_traverse(match, ["awayScore"], default=0),
sezon= safe_traverse(match, ["tournamentSeason"], default=""),
nazwa_turnieju= safe_traverse(match, ["tournamentTitle"], default=""),
skrocona_nazwa_turnieju=safe_traverse(match, ["tournamentTemplateShortCode"], default=""),
flaga= safe_traverse(match, ["flagId"], default=0),
)
match_num += 1
stats=safe_traverse(match, ["stats"], default=""),
self.db.increment_fields("sportowcy",zewnetrzne_id_sportowca,
ostatni_mecz= safe_traverse(match, ["eventId"], default=0), #TODO: Zaktualizuj statystyki sportowca
ilosc_wystapien= safe_traverse(match, ["eventId"], default=0), #TODO: Zaktualizuj statystyki sportowca
minut_gry= get_stat_value(stats, "595"),
gier_sum= 1 if get_stat_value(stats, "595") > 0 else 0 ,
goli_sum= get_stat_value(stats, "596"),
asyst_sum= get_stat_value(stats, "541"),
interwencji_sum= safe_traverse(match, ["eventId"], default=0), #TODO: Zaktualizuj statystyki sportowca
nieobronionych_interwencji_sum= safe_traverse(match, ["eventId"], default=0), #TODO: Zaktualizuj statystyki sportowca
zoltych_kartek_sum= get_stat_value(stats, "599"),
czerwonych_kartek_sum= get_stat_value(stats, "600"),
wygranych_sum= safe_traverse(match, ["eventId"], default=0), #TODO: Zaktualizuj statystyki sportowca
wynik_sum= safe_traverse(match, ["eventId"], default=0), #TODO: Zaktualizuj statystyki sportowca
meczow_do_wynikow_sum= safe_traverse(match, ["eventId"], default=0), #TODO: Zaktualizuj statystyki sportowca
)
# TODO: Zaktualizuj statystyki sportowca
# Opcjonalnie: odczekaj kilka sekund (?)
# Problem w tym, że time.sleep() jest blokujące,
# a asyncio i flask nie idą ze sobą w parze.
# Można to załatwić osobnym skryptem, ale
# martwmy się tym dopiero, gdy dostaniemy
# rate limita. - sherl
page += 1
time.sleep(15)
def aktualizuj_dane(self):
"""
Pobiera mecze dla każdego sportowca wymienionego
w pliku konfiguracyjnym.
"""
for id_sportowca in lewy_globals.config['sportsmen']['tracked_ids']:
self.aktualizuj_dane_sportowca(zewnetrzne_id_sportowca=id_sportowca)
time.sleep(15)