1104 lines
41 KiB
Go
Executable File
1104 lines
41 KiB
Go
Executable File
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 incidentRow--home odd"><div class="time-box">80'</div><div title="The referee stops play so that a substitution<br />can be made and Lucas (Tottenham) comes<br />onto the pitch for Heung-Min Son." 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/lucas/EcmXnnoo/'); return false;">Lucas</a></span><span class="substitution-out-name"><span class="icon substitution-out"> </span><a href="#" onclick="window.open('/player/son-heung-min/Mgg9oPeM/'); return false;">Son Heung-Min</a></span></div><div class="detailMS__incidentRow incidentRow--away even"><div class="time-box">82'</div><div title="Perfectly taken free kick! Aaron Cresswell<br />sends a cross in and Fabian Balbuena (West<br />Ham) leaps high to connect with it on the<br />edge of the 6-yard box. His close-range<br />header goes inside the right post and he<br />makes it 3:1." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box soccer-ball"><span class="icon soccer-ball"> </span></div><span class="assist note-name">(<a href="#" onclick="window.open('/player/cresswell-aaron/fcMqoMcH/'); return false;">Cresswell A.</a>)</span><span class="participant-name"><a href="#" onclick="window.open('/player/balbuena-fabian/ELMR4PLj/'); return false;">Balbuena F.</a></span></div><div class="detailMS__incidentRow incidentRow--away odd"><div class="time-box">85'</div><div title="It's an own goal! An unlucky moment for<br />Davinson Sanchez (Tottenham), who tries<br />to clear a cross into the six-yard box but<br />heads the ball behind his own keeper. The<br />score is 3:2." onmouseover="tt.show(this, event, false)" onmouseout="tt.hide(this)" class="icon-box soccer-ball-own"><span class="icon soccer-ball-own"> </span></div><span class=" note-name">(Own goal)</span><span class="participant-name"><a href="#" onclick="window.open('/player/sanchez-davinson/dbPhv0VQ/'); return false;">Sanchez D.</a></span></div><div class="detailMS__incidentRow incidentRow--away even"><div class="time-box">89'</div><div title="Paul Tierney shows the yellow card to Arthur<br />Masuaku (West Ham) for a heavy challenge." 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">(Holding)</span><span class="participant-name"><a href="#" onclick="window.open('/player/masuaku-arthur/4YoV0hmC/'); return false;">Masuaku A.</a></span></div><div class="detailMS__incidentRow incidentRow--away odd"><div class="time-box">90'</div><div title="The referee signals a substitution. Robert<br />Snodgrass (West Ham) is brought on as a<br />substitute for Arthur Masuaku." 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/snodgrass-robert/6XPsH2mg/'); return false;">Snodgrass R.</a></span><span class="substitution-out-name"><a href="#" onclick="window.open('/player/masuaku-arthur/4YoV0hmC/'); return false;">Masuaku A.</a><span class="icon substitution-out"> </span></span></div><div class="detailMS__incidentRow incidentRow--away even"><div class="time-box-wide">90+4'</div><div title="Manuel Lanzini (West Ham) finishes with<br />a fabulous long-range hammer that bounces<br />off the post and into the top right corner.<br />What a wonderful goal. The keeper was completely<br />helpless." onmouseover="tt.show(this, event, false)" 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/lanzini-manuel/QT3lIj4N/'); return false;">Lanzini M.</a></span></div><div class="detailMS__incidentRow incidentRow--away odd"><div class="time-box-wide">90+5'</div><div class="icon-box y-card"><span class="icon y-card"> </span></div><span class="subincident-penalty subincident-name">(Unsportsmanlike conduct)</span><span class="participant-name"><a href="#" onclick="window.open('/player/lanzini-manuel/QT3lIj4N/'); return false;">Lanzini M.</a></span></div></div><div class="match-information-wrapper"><div class="parts match-information"><div><div class="stage-header"><div class="h-part">Match Information</div></div><div class="match-information-data"><div class="content">Referee: Tierney P. (Eng), </div><div class="content">Venue: Tottenham Hotspur Stadium (London)</div></div></div></div></div><div id="secret_hash"></div>
|
||
|
||
//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
|
||
}
|
||
*/
|