269 lines
12 KiB
Python
269 lines
12 KiB
Python
from flask import session
|
|
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"safe_traverse: error reading {' -> '.join(path)} - returning: {default}")
|
|
finally:
|
|
return result
|
|
|
|
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 to_iso_compatible_date(self, dmy_date: str):
|
|
"""
|
|
Zamienia datę z formatu DD.MM.YY na YYYY-MM-DD
|
|
|
|
:param dmy_date: Data w formacie DD.MM.YY
|
|
:type dmy_date: str
|
|
"""
|
|
day, month, year = dmy_date.split(".")
|
|
return f"{2000 + int(year)}-{month}-{day}"
|
|
|
|
|
|
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).
|
|
|
|
id_zawodnika = self.db.get_id_zawodnika_by_zewnetrzne_id(zewnetrzne_id_sportowca)
|
|
zawodnik = self.db.simple_select_all("sportowcy", zewnetrzne_id_zawodnika=zewnetrzne_id_sportowca)[0]
|
|
|
|
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~
|
|
#
|
|
# TODO: UWAGA! Nie powinniśmy tak tego sprawdzać!
|
|
# To sprawdzenie powinno jedynie służyć zapobieganiu dodania istniejących meczy,
|
|
# natomiast istniejący mecz nie oznacza, że sportowiec ma już statystykę z niego!
|
|
# Przerwać scrapowanie należy wtedy, gdy znajdzie się statystykę sportowca
|
|
# z bieżącego meczu, a nie kiedy znajdzie się bieżący mecz w bazie!
|
|
#
|
|
# TODO TODO TODO FIXME FIXME FIXME
|
|
# 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}")
|
|
|
|
iso_converted_date = self.to_iso_compatible_date(safe_traverse(match, ["eventStartTime"], default="1970-01-01"))
|
|
|
|
# self.db.simple_insert_one("mecze",
|
|
# zewnetrzne_id_meczu = safe_traverse(match, ["eventEncodedId"], default=""),
|
|
# data = iso_converted_date,
|
|
# gospodarze_id = home_club_id,
|
|
# gospodarze = self.db.simple_select_all("kluby", id_klubu=home_club_id)[0],
|
|
# goscie_id = away_club_id,
|
|
# goscie = self.db.simple_select_all("kluby", id_klubu=away_club_id)[0],
|
|
# 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="")
|
|
zewnetrzne_id_meczu = safe_traverse(match, ["eventEncodedId"], default="")
|
|
|
|
if stats != False: # gdy sportowiec był aktywny w meczu
|
|
print("todo :)")
|
|
# todo:
|
|
self.db.simple_insert_one("sportowcy_w_meczach",
|
|
id_zawodnika = id_zawodnika,
|
|
zawodnik = zawodnik,
|
|
zewnetrzne_id_meczu = zewnetrzne_id_meczu,
|
|
czas_gry = int("0" + safe_traverse(stats, ["595", "value"], default="0").rstrip("'?")),
|
|
goli = int(safe_traverse(stats, ["596", "value"], default="0")),
|
|
asyst = int(safe_traverse(stats, ["541", "value"], default="0")),
|
|
interwencje_bramkarza = 0,
|
|
suma_interwencji_na_bramke = 0,
|
|
zolte_kartki = int(safe_traverse(stats, ["599", "value"], default="0")),
|
|
czerwone_kartki = int(safe_traverse(stats, ["600", "value"], default="0")),
|
|
wygrana = {"Z": 1, "R": 0, "P": -1}.get(safe_traverse(match, ["winLoseShort"], default=""), 0),
|
|
wynik = safe_traverse(match, ["rating"], default=0) or 0
|
|
)
|
|
|
|
# # analogicznie zinkrementuj statystyki_sportowcow:
|
|
# # uwaga! nie zadziała dla nikogo innego, niż robercika (bo nie mamy innych sportowców w bazie, trzeba dodać ich ręcznie w lewy_db sample_data_init())
|
|
# self.db.simple_insert_one("statystyki_sportowcow",
|
|
# sportowiec = zawodnik,
|
|
# ostatni_mecz = self.db.get_id_meczu_by_zewnetrzne_id(zewnetrzne_id_meczu),
|
|
# ilosc_wystapien = 1 if int(safe_traverse(stats, ["595", "value"], default="0").rstrip("'")) > 0 else 0,
|
|
# minut_gry = int(safe_traverse(stats, ["595", "value"], default="0").rstrip("'")),
|
|
# gier_sum = 1 if int(safe_traverse(stats, ["595", "value"], default="0").rstrip("'")) > 0 else 0,
|
|
# goli_sum = int(safe_traverse(stats, ["596", "value"], default="0")),
|
|
# asyst_sum = int(safe_traverse(stats, ["541", "value"], default="0")),
|
|
# interwencji_sum = 0,
|
|
# nieobronionych_interwencji_sum = 0,
|
|
# zoltych_kartek_sum = int(safe_traverse(stats, ["599", "value"], default="0")),
|
|
# czerwonych_kartek_sum = int(safe_traverse(stats, ["600", "value"], default="0")),
|
|
# wygranych_sum = 1 if safe_traverse(match, ["winloseshort"], default="") == "z" else 0,
|
|
# wynik_sum = safe_traverse(match, ["rating"], default=0),
|
|
# meczow_do_wynikow_sum = 1 if safe_traverse(match, ["rating"], default=0) not in (0, none) else none
|
|
# )
|
|
|
|
else:
|
|
print("też todo :)")
|
|
# # TODO: TU TEŻ TRZEBA POPRAWIĆ ANALOGICZNIE DO TEGO, CO JEST WEWNĄTRZ IF'A
|
|
self.db.simple_insert_one("sportowcy_w_meczach",
|
|
id_zawodnika = id_zawodnika,
|
|
zawodnik = zawodnik,
|
|
zewnetrzne_id_meczu = zewnetrzne_id_meczu,
|
|
czas_gry = 0, #(lambda v: int(str(v).rstrip("'")) if isinstance(v, (str, int, float)) and str(v).rstrip("'").lstrip("-").isdigit() else 0)(safe_traverse(stats if isinstance(stats, dict) else {}, ["595", "value"], default="0")),
|
|
goli = int(safe_traverse(stats, ["596", "value"], default="0")),
|
|
asyst = int(safe_traverse(stats, ["541", "value"], default="0")),
|
|
interwencje_bramkarza = 0,
|
|
suma_interwencji_na_bramke = 0,
|
|
zolte_kartki = int(safe_traverse(stats, ["599", "value"], default="0")),
|
|
czerwone_kartki = int(safe_traverse(stats, ["600", "value"], default="0")),
|
|
wygrana = {"Z": 1, "R": 0, "P": -1}.get(safe_traverse(match, ["winLoseShort"], default=""), 0),
|
|
wynik = safe_traverse(match, ["rating"], default=0) or 0
|
|
)
|
|
|
|
# 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(5)
|
|
|
|
|
|
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)
|
|
|