1104 lines
41 KiB
Go
1104 lines
41 KiB
Go
|
|
package flashscore
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bytes"
|
|||
|
|
"fmt"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/PuerkitoBio/goquery"
|
|||
|
|
"gordenko.dev/dima/flashscore/model"
|
|||
|
|
webspider "gordenko.dev/dima/spider"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// ListOfScheduledMatches - загружает списки матчей на сегодня и завтра
|
|||
|
|
func (s *Flashscore) ListOfScheduledMatches(sportID int) (result []ScheduledMatch, err error) {
|
|||
|
|
|
|||
|
|
spider, err := webspider.NewSpider(nil)
|
|||
|
|
if err != nil {
|
|||
|
|
panic(err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
referer string
|
|||
|
|
urls []string
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
switch sportID {
|
|||
|
|
case Football:
|
|||
|
|
urls = []string{
|
|||
|
|
footballAllGamesTodayURL,
|
|||
|
|
footballAllGamesTomorrowURL,
|
|||
|
|
}
|
|||
|
|
referer = "https://www.flashscore.com/football/"
|
|||
|
|
|
|||
|
|
case Handball:
|
|||
|
|
urls = []string{
|
|||
|
|
handballAllGamesTodayURL,
|
|||
|
|
handballAllGamesTomorrowURL,
|
|||
|
|
}
|
|||
|
|
referer = "https://www.flashscore.com/handball/"
|
|||
|
|
|
|||
|
|
case Tennis:
|
|||
|
|
urls = []string{
|
|||
|
|
tennisAllGamesTodayURL,
|
|||
|
|
tennisAllGamesTomorrowURL,
|
|||
|
|
}
|
|||
|
|
referer = "https://www.flashscore.com/tennis/"
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Загружаем AJAX страницы со списками матчей
|
|||
|
|
|
|||
|
|
for _, allGamesURL := range urls {
|
|||
|
|
var (
|
|||
|
|
buf []byte
|
|||
|
|
list []ScheduledMatch
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
buf, err = spider.Get(allGamesURL, map[string]string{
|
|||
|
|
"referer": "https://d.flashscore.com/x/feed/proxy-local",
|
|||
|
|
"x-requested-with": "XMLHttpRequest",
|
|||
|
|
// Важное поле! Код можно найти в исходниках либо подсмотреть. Без кода
|
|||
|
|
// сервер отдает 401 ошибку
|
|||
|
|
"x-fsign": feedSign,
|
|||
|
|
"x-referer": referer,
|
|||
|
|
"x-geoip": "1",
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("Load all Games feed: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//fmt.Printf("%s\n\n", buf)
|
|||
|
|
|
|||
|
|
list, err = parseFlashscoreEncodedAllGames(string(buf), sportID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("parseListOfScheduledMatches: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result = append(result, list...)
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Flashscore) SyncUpcomingMatchReport(report *UpcomingMatchReport) (err error) {
|
|||
|
|
spider, err := webspider.NewSpider(nil)
|
|||
|
|
if err != nil {
|
|||
|
|
panic(err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Проверяем зону
|
|||
|
|
storedZone, err := s.model.GetTeamZone(report.Zone.ZoneID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedZone == nil {
|
|||
|
|
err = s.model.AddTeamZone(report.Zone)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Проверяем чемпионат
|
|||
|
|
storedChamp, err := s.model.GetTeamChamp(report.Champ.ChampID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedChamp == nil {
|
|||
|
|
// Загружаем страницу чемпионата
|
|||
|
|
var champ model.TeamChamp
|
|||
|
|
|
|||
|
|
champ, err = s.loadTeamChamp(report.Champ.ChampID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = s.model.AddTeamChamp(champ)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
report.Champ = champ
|
|||
|
|
} else {
|
|||
|
|
report.Champ = *storedChamp
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !report.IsTeamsLoaded {
|
|||
|
|
var storedHome, storedAway *model.Team
|
|||
|
|
|
|||
|
|
report.Home, report.Away, err = loadHomeAndAwayTeams(spider, report.MatchID, report.SportID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("loadHomeAndAwayTeams: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Не забываем добавить зону!
|
|||
|
|
report.Home.ZoneID = report.Zone.ZoneID
|
|||
|
|
report.Away.ZoneID = report.Zone.ZoneID
|
|||
|
|
|
|||
|
|
storedHome, err = s.model.GetTeam(report.Home.TeamID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedHome == nil {
|
|||
|
|
err = s.model.AddTeam(report.Home)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
storedAway, err = s.model.GetTeam(report.Away.TeamID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedAway == nil {
|
|||
|
|
err = s.model.AddTeam(report.Away)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Команды успешно загружены
|
|||
|
|
report.IsTeamsLoaded = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !report.IsHistoryLoaded {
|
|||
|
|
// ВАЖНО!
|
|||
|
|
// На странице AllGames можно получить названия команд, но там нет TeamID!
|
|||
|
|
// Находим на странице названия и TeamID.
|
|||
|
|
|
|||
|
|
report.HomeTeamMatches, report.AwayTeamMatches, report.H2H, err = loadH2H(
|
|||
|
|
spider, report.MatchID, report.SportID,
|
|||
|
|
report.Home.CanonicalName, report.Away.CanonicalName)
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("Load H2H: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// STATS
|
|||
|
|
|
|||
|
|
switch report.SportID {
|
|||
|
|
case Handball, Football:
|
|||
|
|
report.HomeTeamMatches = s.addScoreStatToTeamMatches(report.HomeTeamMatches, report.SportID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
report.AwayTeamMatches = s.addScoreStatToTeamMatches(report.AwayTeamMatches, report.SportID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
report.H2H = s.addScoreStatToTeamMatches(report.H2H, report.SportID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//buf, _ = json.MarshalIndent(report.HomeTeamMatches, "", " ")
|
|||
|
|
//fmt.Printf("HOME:\n\n%s\n\n", buf)
|
|||
|
|
|
|||
|
|
//buf, _ = json.MarshalIndent(report.AwayTeamMatches, "", " ")
|
|||
|
|
//fmt.Printf("AWAY:\n\n%s\n\n", buf)
|
|||
|
|
|
|||
|
|
//buf, _ = json.MarshalIndent(report.H2H, "", " ")
|
|||
|
|
//fmt.Printf("H2H:\n\n%s\n\n", buf)
|
|||
|
|
|
|||
|
|
case Tennis:
|
|||
|
|
// passs
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
report.IsHistoryLoaded = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ODDS (Загружаем всегда, чтобы иметь свежие данные! )
|
|||
|
|
var (
|
|||
|
|
someErr error
|
|||
|
|
funcName string
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
switch report.SportID {
|
|||
|
|
case Football:
|
|||
|
|
funcName = "loadFootballOdds"
|
|||
|
|
report.Odds, someErr = loadFootballOdds(spider, report.MatchID)
|
|||
|
|
|
|||
|
|
case Handball:
|
|||
|
|
funcName = "loadHandballOdds"
|
|||
|
|
report.Odds, someErr = loadHandballOdds(spider, report.MatchID)
|
|||
|
|
|
|||
|
|
case Tennis:
|
|||
|
|
funcName = "loadTennisOdds"
|
|||
|
|
report.Odds, someErr = loadHandballOdds(spider, report.MatchID)
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
panic(fmt.Sprintf("Bug: odds loader for the sportID %d is undefined", report.SportID))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Если не загрузились odds - это не страшно. Ошибку возвращать нельзя,
|
|||
|
|
// ибо IsCompleted станет false
|
|||
|
|
if someErr != nil {
|
|||
|
|
s.logger.Printf("%s: %s", funcName, someErr)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
func (s *Flashscore) parseMatchInfo(doc *goquery.Document, sportID int) (zone model.TeamZone, champ model.TeamChamp, home, away model.Team, err error) {
|
|||
|
|
// Home team
|
|||
|
|
home, err = parseTeam(doc.Find(".tname-home"))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("Parse home team: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
// Away team
|
|||
|
|
away, err = parseTeam(doc.Find(".tname-away"))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("Parse away team: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
///////////////////////////
|
|||
|
|
|
|||
|
|
// Проверяем чемпионат
|
|||
|
|
storedChamp, err := s.model.GetTeamChamp(champ.ChampID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedChamp == nil {
|
|||
|
|
// Проверяем зону
|
|||
|
|
var storedZone *model.TeamZone
|
|||
|
|
|
|||
|
|
storedZone, err = s.model.GetTeamZone(zone.ZoneID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedZone == nil {
|
|||
|
|
err = s.model.AddTeamZone(zone)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = s.model.AddTeamChamp(champ)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Сайт меняет названия чемпионатов по ходу чемпионата. Например:
|
|||
|
|
// Лига Европы 1/128
|
|||
|
|
// Лига Европы. Плэй-офф 1/4
|
|||
|
|
//
|
|||
|
|
// Поэтому названия нужно обновлять
|
|||
|
|
champ.Name = strings.TrimSpace(champ.Name)
|
|||
|
|
|
|||
|
|
if storedChamp.Name != champ.Name {
|
|||
|
|
err = s.model.UpdateTeamChampName(champ)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
storedHome, err := s.model.GetTeam(home.TeamID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedHome == nil {
|
|||
|
|
home.ZoneID, err = s.loadTeamZoneID(home.TeamID, sportID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("loadTeamZoneID: %s; teamID=%s", err, home.TeamID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = s.model.AddTeam(home)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
home.ZoneID = storedHome.ZoneID
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
storedAway, err := s.model.GetTeam(away.TeamID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedAway == nil {
|
|||
|
|
away.ZoneID, err = s.loadTeamZoneID(away.TeamID, sportID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("loadTeamZoneID: %s; teamID=%s", err, away.TeamID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = s.model.AddTeam(away)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
away.ZoneID = storedAway.ZoneID
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
func (s *Flashscore) loadTeamZoneID(teamID string, sportID int) (_ string, err error) {
|
|||
|
|
teamURL := fmt.Sprintf(teamURLPattern, teamID)
|
|||
|
|
|
|||
|
|
var buf []byte
|
|||
|
|
buf, err = s.spider.Get(teamURL, nil)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("Load footballTeamURL: %s; url=%s", err, teamURL)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("NewDocumentFromReader: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
zone, err := parseZoneOnTeamPage(doc, sportID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("parseZoneIDOnTeamPage: %s; teamID=%s", err, teamID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
storedZone, err := s.model.GetTeamZone(zone.ZoneID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedZone == nil {
|
|||
|
|
err = s.model.AddTeamZone(zone)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return zone.ZoneID, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Flashscore) addScoreStatToTeamMatches(before []model.TeamMatch, sportID int) (after []model.TeamMatch) {
|
|||
|
|
for idx, result := range before {
|
|||
|
|
// Сокращаем количество матчей для анализа
|
|||
|
|
if idx >= 10 {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
loadErr error
|
|||
|
|
// Для логов
|
|||
|
|
loadFuncName string
|
|||
|
|
homeGoals, awayGoals int
|
|||
|
|
// Чтобы узнать что какие-то данные были загружены и можно сохранить
|
|||
|
|
// результат в БД
|
|||
|
|
isFlagsChanged bool
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
storedMatch, err := s.model.GetTeamMatch(result.MatchID)
|
|||
|
|
if err != nil {
|
|||
|
|
s.logger.Printf("GetTeamMatch: %s\n", err)
|
|||
|
|
} else {
|
|||
|
|
if storedMatch != nil {
|
|||
|
|
result = *storedMatch
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// СУПЕР ВАЖНО!
|
|||
|
|
// Если счет в матче отсканирован заранее, например в одной из таблиц H2H,
|
|||
|
|
// важно выставить флаг IsMatchScoreLoaded = true. Иначе, например, для
|
|||
|
|
// футбола в следующем блоке кода счет будет перезаписан пустыми значениями.
|
|||
|
|
if !result.IsMatchScoreLoaded {
|
|||
|
|
// Основной счет
|
|||
|
|
switch sportID {
|
|||
|
|
case Football:
|
|||
|
|
//loadFuncName = "loadFootballTeamScoreByPeriods"
|
|||
|
|
//stat, loadErr = loadFootballTeamScoreByPeriods(s.spider, result.MatchID)
|
|||
|
|
|
|||
|
|
case Handball:
|
|||
|
|
loadFuncName = "loadHandballMatchScore"
|
|||
|
|
homeGoals, awayGoals, loadErr = loadHandballMatchScore(s.spider, result.MatchID)
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
panic(fmt.Sprintf("Bug: missing data loader for the sportID %d is undefined", sportID))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if loadErr != nil {
|
|||
|
|
// Только логгируем
|
|||
|
|
s.logger.Printf("%s: %s; matchID=%s\n", loadFuncName, loadErr, result.MatchID)
|
|||
|
|
|
|||
|
|
// Переходим к следующему матчу
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result.HomeGoals = homeGoals
|
|||
|
|
result.AwayGoals = awayGoals
|
|||
|
|
|
|||
|
|
// Флаг, который покажет, что матч нужно пересканировать, если счет не загружен
|
|||
|
|
result.IsMatchScoreLoaded = true
|
|||
|
|
isFlagsChanged = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !result.IsScoreByPeriodsLoaded {
|
|||
|
|
var (
|
|||
|
|
stat teamScoreByPeriods
|
|||
|
|
hasScoreByPeriods bool
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Отдельные функции потому что, статистика по футболу возвращается
|
|||
|
|
// на HTML странице, по гандболу - в "фирменном" Flashscore формате.
|
|||
|
|
// И те и другие данные нужно загружать по разным адресам.
|
|||
|
|
|
|||
|
|
// Счет по периодам/таймам
|
|||
|
|
switch sportID {
|
|||
|
|
case Football:
|
|||
|
|
loadFuncName = "loadFootballTeamScoreByPeriods"
|
|||
|
|
stat, hasScoreByPeriods, loadErr = loadFootballTeamScoreByPeriods(s.spider, result.MatchID)
|
|||
|
|
|
|||
|
|
case Handball:
|
|||
|
|
loadFuncName = "loadHandballTeamScoreByPeriods"
|
|||
|
|
stat, hasScoreByPeriods, loadErr = loadHandballTeamScoreByPeriods(s.spider, result.MatchID)
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
panic(fmt.Sprintf("Bug: missing data loader for the sportID %d is undefined", sportID))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if loadErr != nil {
|
|||
|
|
// Только логгируем
|
|||
|
|
s.logger.Printf("%s: %s; matchID=%s\n", loadFuncName, loadErr, result.MatchID)
|
|||
|
|
// Переходим к следующему матчу
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if hasScoreByPeriods {
|
|||
|
|
result.P1HomeGoals = stat.P1HomeGoals
|
|||
|
|
result.P1AwayGoals = stat.P1AwayGoals
|
|||
|
|
result.P2HomeGoals = stat.P2HomeGoals
|
|||
|
|
result.P2AwayGoals = stat.P2AwayGoals
|
|||
|
|
|
|||
|
|
result.HasScoreByPeriods = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Частый вариант в гандболе - когда статистика по периодам успешно
|
|||
|
|
// загружена, но данных нет. Поэтому ставим отдельно 2 флага:
|
|||
|
|
// - нужно ли загружать снова
|
|||
|
|
// - статистика по периодам есть/нет
|
|||
|
|
result.IsScoreByPeriodsLoaded = true
|
|||
|
|
isFlagsChanged = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if isFlagsChanged && result.IsMatchScoreLoaded && result.IsScoreByPeriodsLoaded {
|
|||
|
|
// На ошибку не проверяем ибо это просто кэш
|
|||
|
|
s.model.AddTeamMatch(result)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
after = append(after, result)
|
|||
|
|
|
|||
|
|
// Засыпаем на 0.5 сек.
|
|||
|
|
time.Sleep(500 * time.Millisecond)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func loadFootballTeamScoreByPeriods(spider *webspider.Spider, matchID string) (_ teamScoreByPeriods, hasScoreByPeriods bool, err error) {
|
|||
|
|
// Загружаем AJAX страницу со статистикой
|
|||
|
|
url := fmt.Sprintf(footballMatchSummaryURLPattern, matchID)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
buf []byte
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
buf, err = spider.Get(url, map[string]string{
|
|||
|
|
"x-requested-with": "XMLHttpRequest",
|
|||
|
|
"x-fsign": feedSign,
|
|||
|
|
"x-geoip": "1",
|
|||
|
|
"x-referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Страничка имеет вид
|
|||
|
|
//
|
|||
|
|
// <div class="detailMS"><div class="detailMS__incidentsHeader stage-12"><div class="detailMS__headerText">1st Half</div><div class="detailMS__headerScore"><span
|
|||
|
|
// class="p1_home"
|
|||
|
|
// title=""
|
|||
|
|
// >3
|
|||
|
|
// </span> -
|
|||
|
|
// <span
|
|||
|
|
// class="p1_away"
|
|||
|
|
// title=""
|
|||
|
|
// >0
|
|||
|
|
// </span></div></div><div class="detailMS__incidentRow incidentRow--home odd"><div class="time-box">1'</div><div title="Heung-Min Son (Tottenham) with a wonderful<br />piece of finishing. He makes a yard for<br />himself after a well-taken pass inside the<br />box and nets into the bottom right corner." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box soccer-ball"><span class="icon soccer-ball"> </span></div><span class="participant-name"><a href="#" onclick="window.open('/player/son-heung-min/Mgg9oPeM/'); return false;">Son Heung-Min</a></span><span class="assist note-name">(<a href="#" onclick="window.open('/player/kane-harry/v5HSlEAa/'); return false;">Kane H.</a>)</span></div><div class="detailMS__incidentRow incidentRow--home even"><div class="time-box">8'</div><div title="Harry Kane (Tottenham) breaks through before<br />collecting a brilliant pass and producing<br />a precise strike to beat the goalkeeper.<br />The ball went into the bottom left corner." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box soccer-ball"><span class="icon soccer-ball"> </span></div><span class="participant-name"><a href="#" onclick="window.open('/player/kane-harry/v5HSlEAa/'); return false;">Kane H.</a></span><span class="assist note-name">(<a href="#" onclick="window.open('/player/son-heung-min/Mgg9oPeM/'); return false;">Son Heung-Min</a>)</span></div><div class="detailMS__incidentRow incidentRow--home odd"><div class="time-box">16'</div><div title="A perfect lofted pass from Sergio Reguilon<br />finds Harry Kane (Tottenham), who jumps<br />highest and steers a close-range header<br />into the left side of the goal. His effort<br />was both strong and precise, and Lukasz<br />Fabianski had no chance of stopping that.<br />3:0." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box soccer-ball"><span class="icon soccer-ball"> </span></div><span class="participant-name"><a href="#" onclick="window.open('/player/kane-harry/v5HSlEAa/'); return false;">Kane H.</a></span><span class="assist note-name">(<a href="#" onclick="window.open('/player/reguilon-sergio/l0lnJteM/'); return false;">Reguilon S.</a>)</span></div><div class="detailMS__incidentRow incidentRow--away even"><div class="time-box">20'</div><div title="This yellow card was deserved. The tackle<br />by Michail Antonio (West Ham) was quite<br />harsh and Paul Tierney didn't hesitate to<br />show him a yellow card." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box y-card"><span class="icon y-card"> </span></div><span class="subincident-penalty subincident-name">(Tripping)</span><span class="participant-name"><a href="#" onclick="window.open('/player/antonio-michail/p8tt85Xn/'); return false;">Antonio M.</a></span></div><div class="detailMS__incidentsHeader stage-13"><div class="detailMS__headerText">2nd Half</div><div class="detailMS__headerScore"><span
|
|||
|
|
// class="p2_home"
|
|||
|
|
// title=""
|
|||
|
|
// >0
|
|||
|
|
// </span> -
|
|||
|
|
// <span
|
|||
|
|
// class="p2_away"
|
|||
|
|
// title=""
|
|||
|
|
// >3
|
|||
|
|
// </span></div></div><div class="detailMS__incidentRow incidentRow--away odd"><div class="time-box">72'</div><div title="Angelo Ogbonna (West Ham) is booked after<br />bringing down an opponent. Paul Tierney<br />had an easy-decision to make." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box y-card"><span class="icon y-card"> </span></div><span class="subincident-penalty subincident-name">(Roughing)</span><span class="participant-name"><a href="#" onclick="window.open('/player/ogbonna-angelo/xtEnn3rQ/'); return false;">Ogbonna A.</a></span></div><div class="detailMS__incidentRow incidentRow--home even"><div class="time-box">72'</div><div title="The referee stops play so that a substitution<br />can be made and Gareth Bale (Tottenham)<br />replaces Steven Bergwijn." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box substitution-in"><span class="icon substitution-in"> </span></div><span class="substitution-in-name"><a href="#" onclick="window.open('/player/bale-gareth/4SPjLLiR/'); return false;">Bale G.</a></span><span class="substitution-out-name"><span class="icon substitution-out"> </span><a href="#" onclick="window.open('/player/bergwijn-steven/Kdj2xxJR/'); return false;">Bergwijn S.</a></span></div><div class="detailMS__incidentRow incidentRow--home odd"><div class="time-box">73'</div><div title="Jose Mourinho has decided to make a change.<br />Harry Winks (Tottenham) replaces Tanguy<br />Ndombele." onmouseover="tt.show(this, event, true)" onmouseout="tt.hide(this)" class="icon-box substitution-in"><span class="icon substitution-in"> </span></div><span class="substitution-in-name"><a href="#" onclick="window.open('/player/winks-harry/MRebAusr/'); return false;">Winks H.</a></span><span class="substitution-out-name"><span class="icon substitution-out"> </span><a href="#" onclick="window.open('/player/ndombele-tanguy/GEIWE1pD/'); return false;">Ndombele T.</a></span></div><div class="detailMS__incidentRow incidentRow--away even"><div class="time-box">77'</div><div title="The foul by Tomas Soucek (West Ham) is worthy<br />of a card and a yellow is duly shown by<br />Paul Tierney." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box y-card"><span class="icon y-card"> </span></div><span class="subincident-penalty subincident-name">(Roughing)</span><span class="participant-name"><a href="#" onclick="window.open('/player/soucek-tomas/8zWzYFRF/'); return false;">Soucek T.</a></span></div><div class="detailMS__incidentRow incidentRow--away odd"><div class="time-box">77'</div><div title="Substitution. Andriy Yarmolenko (West Ham)<br />has come on for Michail Antonio." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box substitution-in"><span class="icon substitution-in"> </span></div><span class="substitution-in-name"><a href="#" onclick="window.open('/player/yarmolenko-andriy/O00J9ZLN/'); return false;">Yarmolenko A.</a></span><span class="substitution-out-name"><a href="#" onclick="window.open('/player/antonio-michail/p8tt85Xn/'); return false;">Antonio M.</a><span class="icon substitution-out"> </span></span></div><div class="detailMS__incidentRow incidentRow--away even"><div class="time-box">77'</div><div title="Here is a change. Pablo Fornals is going<br />off and David William Moyes gives the last<br />tactical orders to Manuel Lanzini (West<br />Ham)." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box substitution-in"><span class="icon substitution-in"> </span></div><span class="substitution-in-name"><a href="#" onclick="window.open('/player/lanzini-manuel/QT3lIj4N/'); return false;">Lanzini M.</a></span><span class="substitution-out-name"><a href="#" onclick="window.open('/player/fornals-pablo/CfghB0gf/'); return false;">Fornals P.</a><span class="icon substitution-out"> </span></span></div><div class="detailMS__incidentRow incid
|
|||
|
|
|
|||
|
|
//fmt.Printf("SCORE BY PERIODS:\n%s\n\n", buf)
|
|||
|
|
|
|||
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("goquery.NewDocumentFromReader: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return parseFootballTeamScoreByPeriods(doc)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Football
|
|||
|
|
|
|||
|
|
func loadFootballOdds(spider *webspider.Spider, matchID string) (odds Odds, err error) {
|
|||
|
|
url := fmt.Sprintf(footballOddsURLPattern, matchID)
|
|||
|
|
|
|||
|
|
buf, err := spider.Get(url, map[string]string{
|
|||
|
|
"x-requested-with": "XMLHttpRequest",
|
|||
|
|
"x-fsign": feedSign,
|
|||
|
|
"x-geoip": "1",
|
|||
|
|
"referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("Load football odds: %s; url=%s", err, url)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("goquery.NewDocumentFromReader: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
odds.FullTimeTotal, err = parseOddsTotal(doc)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("parseOddsTotal: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// odds 1x2 full time
|
|||
|
|
divOdds1x2FT := doc.Find("#block-1x2-ft")
|
|||
|
|
if divOdds1x2FT.Length() == 0 {
|
|||
|
|
err = fmt.Errorf(`Div #block-1x2-ft not found`)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tableOdds1x2FT := divOdds1x2FT.First().Find("#odds_1x2")
|
|||
|
|
if tableOdds1x2FT.Length() == 0 {
|
|||
|
|
err = fmt.Errorf(`<table id="odds_1x2"> not found`)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
odds.Winner, err = parseOdds1x2(tableOdds1x2FT)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("parseOdds1x2: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// /fmt.Printf("%# v\n", pretty.Formatter(offers))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Handball
|
|||
|
|
func loadHandballOdds(spider *webspider.Spider, matchID string) (odds Odds, err error) {
|
|||
|
|
url := fmt.Sprintf(handballOddsURLPattern, matchID)
|
|||
|
|
|
|||
|
|
buf, err := spider.Get(url, map[string]string{
|
|||
|
|
"x-requested-with": "XMLHttpRequest",
|
|||
|
|
"x-fsign": feedSign,
|
|||
|
|
"x-geoip": "1",
|
|||
|
|
"referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("Load handball odds: %s; url=%s", err, url)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
odds, err = parseFlashscoreEncodedOdds(string(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func loadHandballMatchScore(spider *webspider.Spider, matchID string) (homeGoals, awayGoals int, err error) {
|
|||
|
|
url := fmt.Sprintf(handballMatchScoreURLPattern, matchID)
|
|||
|
|
|
|||
|
|
buf, err := spider.Get(url, map[string]string{
|
|||
|
|
"x-requested-with": "XMLHttpRequest",
|
|||
|
|
"x-fsign": feedSign,
|
|||
|
|
"x-geoip": "1",
|
|||
|
|
"x-referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return parseHandballMatchScore(string(buf))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func loadHandballTeamScoreByPeriods(spider *webspider.Spider, matchID string) (_ teamScoreByPeriods, hasScoreByPeriods bool, err error) {
|
|||
|
|
// Загружаем AJAX страницу со статистикой
|
|||
|
|
url := fmt.Sprintf(handballScoreByPeriodsURLPattern, matchID)
|
|||
|
|
|
|||
|
|
buf, err := spider.Get(url, map[string]string{
|
|||
|
|
"x-requested-with": "XMLHttpRequest",
|
|||
|
|
"x-fsign": feedSign,
|
|||
|
|
"x-geoip": "1",
|
|||
|
|
"x-referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return parseHandballTeamScoreByPeriods(string(buf))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func loadFootballH2H(spider *webspider.Spider, matchID string) (homeTeamMatches, awayTeamMatches, h2hMatches []model.TeamMatch, err error) {
|
|||
|
|
// Загружаем AJAX страницу с таблицами матчей
|
|||
|
|
url := fmt.Sprintf(footballH2HTablesURLPattern, matchID)
|
|||
|
|
|
|||
|
|
buf, err := spider.Get(url, map[string]string{
|
|||
|
|
"referer": "https://d.flashscore.com/x/feed/proxy-local",
|
|||
|
|
"x-requested-with": "XMLHttpRequest",
|
|||
|
|
// Важное поле! Код можно найти в исходниках либо подсмотреть. Без кода
|
|||
|
|
// сервер отдает 401 ошибку
|
|||
|
|
"x-fsign": feedSign,
|
|||
|
|
"x-referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
|
|||
|
|
"x-geoip": "1",
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("load h2hTables: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("goquery.NewDocumentFromReader: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
// var (dirtyTeamID string
|
|||
|
|
|
|||
|
|
// OVERALL - последние матчи команд (хозяев и гостей)
|
|||
|
|
// Внутри блока <div id="tab-h2h-overall" будут 2 таблицы:
|
|||
|
|
// <table class="head_to_head h2h_home"
|
|||
|
|
// <table class="head_to_head h2h_away"
|
|||
|
|
//
|
|||
|
|
// Строки (tr) внутри таблицы имеют вид:
|
|||
|
|
// <tr class="odd highlight"
|
|||
|
|
// <tr class="even highlight"
|
|||
|
|
|
|||
|
|
overall := doc.Find("#tab-h2h-overall")
|
|||
|
|
if overall.Length() == 0 {
|
|||
|
|
err = fmt.Errorf("Div #tab-h2h-overall not found")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
overall = overall.First()
|
|||
|
|
|
|||
|
|
// Последние матчи хозяев (во всех турнирах, на выезде и в гостях)
|
|||
|
|
home := overall.Find(".h2h_home")
|
|||
|
|
if home.Length() == 0 {
|
|||
|
|
err = fmt.Errorf("Table .h2h_home not found")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
homeTeamMatches, err = parseMatchesResults(home.First().Find(".highlight"))
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Последние матчи AWAY команды (во всех турнирах, на выезде и в гостях)
|
|||
|
|
away := overall.Find(".h2h_away")
|
|||
|
|
if away.Length() == 0 {
|
|||
|
|
err = fmt.Errorf("Table .h2h_away not found")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
awayTeamMatches, err = parseMatchesResults(away.First().Find(".highlight"))
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Последние личные встречи (во всех турнирах, на выезде и в гостях)
|
|||
|
|
h2h := overall.Find(".h2h_mutual")
|
|||
|
|
if home.Length() == 0 {
|
|||
|
|
err = fmt.Errorf("Table .h2h_mutual not found")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
h2hMatches, err = parseMatchesResults(h2h.First().Find(".highlight"))
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func loadHandballH2H(spider *webspider.Spider, matchID, homeTeamName, awayTeamName string) (homeTeamMatches, awayTeamMatches, h2hMatches []model.TeamMatch, err error) {
|
|||
|
|
// Загружаем AJAX страницу с историей, закодированную в формате Flashscore
|
|||
|
|
url := fmt.Sprintf(handballH2HTablesURLPattern, matchID)
|
|||
|
|
|
|||
|
|
buf, err := spider.Get(url, map[string]string{
|
|||
|
|
"referer": "https://d.flashscore.com/x/feed/proxy-local",
|
|||
|
|
"x-requested-with": "XMLHttpRequest",
|
|||
|
|
// Важное поле! Код можно найти в исходниках либо подсмотреть. Без кода
|
|||
|
|
// сервер отдает 401 ошибку
|
|||
|
|
"x-fsign": feedSign,
|
|||
|
|
"x-referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
|
|||
|
|
"x-geoip": "1",
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("load h2hTables: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//fmt.Printf("%s\n\n", buf)
|
|||
|
|
|
|||
|
|
homeTeamMatches, awayTeamMatches, h2hMatches, err = parseFlashscoreEncodedH2H(string(buf), homeTeamName, awayTeamName)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Flashscore) loadTeamChamp(champID string) (champ model.TeamChamp, err error) {
|
|||
|
|
// Вычисляем zoneID из champID
|
|||
|
|
parts := strings.Split(champID, "/")
|
|||
|
|
if len(parts) < 3 {
|
|||
|
|
err = fmt.Errorf("Wrong champID %q", champID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
zoneID := strings.Join(parts[:3], "/")
|
|||
|
|
|
|||
|
|
// Загружаем AJAX страницу с историей, закодированную в формате Flashscore
|
|||
|
|
url := fmt.Sprintf(teamChampURLPattern, champID)
|
|||
|
|
|
|||
|
|
buf, err := s.spider.Get(url, map[string]string{
|
|||
|
|
//"referer": "https://d.flashscore.com/x/feed/proxy-local",
|
|||
|
|
//"x-requested-with": "XMLHttpRequest",
|
|||
|
|
// Важное поле! Код можно найти в исходниках либо подсмотреть. Без кода
|
|||
|
|
// сервер отдает 401 ошибку
|
|||
|
|
//"x-fsign": feedSign,
|
|||
|
|
//"x-referer": fmt.Sprintf(h2hLandingURLPattern, matchID),
|
|||
|
|
//"x-geoip": "1",
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("Load team champ page: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// First - на всякий случай (если будет более 1 элемента на странице)
|
|||
|
|
f := doc.Find("div.teamHeader__name").First()
|
|||
|
|
|
|||
|
|
champName := f.Text()
|
|||
|
|
champName = strings.TrimSpace(champName)
|
|||
|
|
|
|||
|
|
return model.TeamChamp{
|
|||
|
|
ChampID: champID,
|
|||
|
|
Name: champName,
|
|||
|
|
ZoneID: zoneID,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
func (s *Flashscore) ListFootballChampNextRoundFixtures(champID string) (_ FootballRound, err error) {
|
|||
|
|
url := fmt.Sprintf(fixturesURLPattern, champID)
|
|||
|
|
|
|||
|
|
buf, err := s.spider.Get(url, nil)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
divEncodedFixtures := doc.Find("#tournament-page-data-fixtures")
|
|||
|
|
|
|||
|
|
if divEncodedFixtures.Length() == 0 {
|
|||
|
|
err = fmt.Errorf("tag #tournament-page-data-fixtures not found")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
node := divEncodedFixtures.Get(0).FirstChild
|
|||
|
|
|
|||
|
|
if node == nil {
|
|||
|
|
// ВАЖНО!
|
|||
|
|
// Это не ошибка - div пустой - матчей нет!
|
|||
|
|
//err = fmt.Errorf("First child of #tournament-page-data-fixtures tag is nil")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if node.Type != xhtml.TextNode {
|
|||
|
|
err = fmt.Errorf("First child of #tournament-page-data-fixtures tag is not TextNode, but %s",
|
|||
|
|
nodeTypes[node.Type])
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
round, err := parseNextRoundFixtures(html.UnescapeString(node.Data))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("parseNextRoundFixtures: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return round, nil
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
func (s *Flashscore) ListFootballChampFixtures(champID string) (rounds []FootballRound, err error) {
|
|||
|
|
url := fmt.Sprintf(fixturesURLPattern, champID)
|
|||
|
|
|
|||
|
|
buf, err := s.spider.Get(url, nil)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
divEncodedFixtures := doc.Find("#tournament-page-data-fixtures")
|
|||
|
|
|
|||
|
|
if divEncodedFixtures.Length() == 0 {
|
|||
|
|
err = fmt.Errorf("tag #tournament-page-data-fixtures not found")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
node := divEncodedFixtures.Get(0).FirstChild
|
|||
|
|
|
|||
|
|
if node.Type != xhtml.TextNode {
|
|||
|
|
err = fmt.Errorf("First child of #tournament-page-data-fixtures tag is not TextNode, but %s",
|
|||
|
|
nodeTypes[node.Type])
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
rounds, err = parseEncodedFixtures(html.UnescapeString(node.Data))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("parseEncodedFixtures: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
func (s *Flashscore) parseMatchInfo(doc *goquery.Document, sportID int) (zone model.TeamZone, champ model.TeamChamp, home, away model.Team, err error) {
|
|||
|
|
zone, champ, home, away, err = parseTeamMatchInfo(doc, sportID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Проверяем чемпионат
|
|||
|
|
storedChamp, err := s.model.GetTeamChamp(champ.ChampID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedChamp == nil {
|
|||
|
|
// Проверяем зону
|
|||
|
|
var storedZone *model.TeamZone
|
|||
|
|
|
|||
|
|
storedZone, err = s.model.GetTeamZone(zone.ZoneID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedZone == nil {
|
|||
|
|
err = s.model.AddTeamZone(zone)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = s.model.AddTeamChamp(champ)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// Сайт меняет названия чемпионатов по ходу чемпионата. Например:
|
|||
|
|
// Лига Европы 1/128
|
|||
|
|
// Лига Европы. Плэй-офф 1/4
|
|||
|
|
//
|
|||
|
|
// Поэтому названия нужно обновлять
|
|||
|
|
champ.Name = strings.TrimSpace(champ.Name)
|
|||
|
|
|
|||
|
|
if storedChamp.Name != champ.Name {
|
|||
|
|
err = s.model.UpdateTeamChampName(champ)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
storedHome, err := s.model.GetTeam(home.TeamID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedHome == nil {
|
|||
|
|
home.ZoneID, err = s.loadTeamZoneID(home.TeamID, sportID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("loadTeamZoneID: %s; teamID=%s", err, home.TeamID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = s.model.AddTeam(home)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
home.ZoneID = storedHome.ZoneID
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
storedAway, err := s.model.GetTeam(away.TeamID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedAway == nil {
|
|||
|
|
away.ZoneID, err = s.loadTeamZoneID(away.TeamID, sportID)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("loadTeamZoneID: %s; teamID=%s", err, away.TeamID)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = s.model.AddTeam(away)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
away.ZoneID = storedAway.ZoneID
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
|
|||
|
|
func (s *Flashscore) addSummaryToFootballMatches(before []model.TeamMatch, sportID int) (hasLostMatch bool, after []model.TeamMatch, err error) {
|
|||
|
|
for idx, result := range before {
|
|||
|
|
// Сокращаем количество матчей для анализа
|
|||
|
|
if idx >= 10 {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Проверяем в БД
|
|||
|
|
var storedMatch *model.TeamMatch
|
|||
|
|
|
|||
|
|
storedMatch, err = s.model.GetTeamMatch(result.MatchID)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if storedMatch != nil {
|
|||
|
|
// Матч найден в БД, заменяем неполную версию в памяти на полную версию из БД
|
|||
|
|
after = append(after, *storedMatch)
|
|||
|
|
} else {
|
|||
|
|
// Матч не найден в БД. Нужно загрузить недостающую информацию:
|
|||
|
|
// - статистика голов по таймам
|
|||
|
|
// - teamID для хозяев и гостей
|
|||
|
|
// - zoneID, champID
|
|||
|
|
|
|||
|
|
// Загружаем Landing
|
|||
|
|
landingURL := fmt.Sprintf(h2hLandingURLPattern, result.MatchID)
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
buf []byte
|
|||
|
|
doc *goquery.Document
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
buf, err = s.spider.Get(landingURL, nil)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("load h2hLanding: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
doc, err = goquery.NewDocumentFromReader(bytes.NewBuffer(buf))
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("goquery.NewDocumentFromReader: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
champ model.TeamChamp
|
|||
|
|
home, away model.Team
|
|||
|
|
)
|
|||
|
|
// Зона не нужна, ибо матч относится к чемпионату
|
|||
|
|
_, champ, home, away, err = s.parseMatchInfo(doc, Football)
|
|||
|
|
if err != nil {
|
|||
|
|
err = fmt.Errorf("parseFootballMatchInfo: %s", err)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
// Важно, если сохраняем результат матча в БД
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
stat teamScoreByPeriods
|
|||
|
|
loadErr error
|
|||
|
|
loadFuncName string
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Отдельные функции потому что, статистика по футболу возвращается
|
|||
|
|
// на HTML странице, по гандболу - в "фирменном" Flashscore формате.
|
|||
|
|
// И те и другие данные нужно загружать по разным адресам.
|
|||
|
|
|
|||
|
|
switch sportID {
|
|||
|
|
case Football:
|
|||
|
|
loadFuncName = "loadFootballTeamScoreByPeriods"
|
|||
|
|
stat, loadErr = loadFootballTeamScoreByPeriods(s.spider, result.MatchID)
|
|||
|
|
|
|||
|
|
case Handball:
|
|||
|
|
loadFuncName = "loadHandballTeamScoreByPeriods"
|
|||
|
|
stat, loadErr = loadHandballTeamScoreByPeriods(s.spider, result.MatchID)
|
|||
|
|
|
|||
|
|
default:
|
|||
|
|
panic(fmt.Sprintf("Bug: missing data loader for the sportID %d is undefined", sportID))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if loadErr != nil {
|
|||
|
|
// Только логгируем
|
|||
|
|
s.logger.Printf("%s: %s; matchID=%s\n", loadFuncName, loadErr, result.MatchID)
|
|||
|
|
// Флаг, который покажет, что матч нужно пересканировать, из-за того, что
|
|||
|
|
// не все матчи загружены
|
|||
|
|
hasLostMatch = true
|
|||
|
|
} else {
|
|||
|
|
|
|||
|
|
result.P1HomeGoals = stat.P1HomeGoals
|
|||
|
|
result.P1AwayGoals = stat.P1AwayGoals
|
|||
|
|
result.P2HomeGoals = stat.P2HomeGoals
|
|||
|
|
result.P2AwayGoals = stat.P2AwayGoals
|
|||
|
|
|
|||
|
|
result.HasStats = true
|
|||
|
|
|
|||
|
|
err = s.model.AddTeamMatch(result)
|
|||
|
|
if err != nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
after = append(after, result)
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
time.Sleep(1 * time.Second)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
*/
|