Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
fcoagz committed Sep 18, 2023
1 parent 96c73d4 commit 5cc2f90
Show file tree
Hide file tree
Showing 14 changed files with 679 additions and 1 deletion.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# conmebol
# conmebol-api
CONMEBOL API te permite obtener información de los resultados, clasificación y próximos partidos referentes a la clasificación de la CONMEBOL para la Copa América.

los datos se obtienen de [onefootball](https://onefootball.com/en/home)

## URL Base
```
https://conmebol-api.vercel.app/
```

## Endpoints
`GET /`: Muestra un mensaje de bienvenida y proporciona un enlace a la documentación de la API.

`GET /api/classification`: Permite obtener las clasificaciones de los países que forman parte de la CONMEBOL.

`GET /api/results`: Permite obtener los resultados de las últimas jornadas de los partidos jugados en la CONMEBOL.

`GET /api/matches`: Permite obtener información sobre los próximos partidos de la CONMEBOL y los partidos que se están jugando en vivo.

### Uso
En el siguiente enlace devolverá un objeto json de los resultados de los últimos días de los partidos jugados.

```
https://conmebol-api.vercel.app/api/results
```
37 changes: 37 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import json

from flask import Flask, render_template
from flask_cors import CORS
from exceptions import page_not_found, internal_server_error
from conmebol import Classification, Results, Matches

app = Flask(__name__)
CORS(app)

app.register_error_handler(404, page_not_found)
app.register_error_handler(500, internal_server_error)

def render_json(_object: dict):
response = app.response_class(
response=json.dumps(_object),
status=200,
mimetype='application/json'
)

return response

@app.route('/')
def index():
return render_template('index.html')

@app.route('/api/classification')
def classification():
return render_json(Classification().get_positions)

@app.route('/api/results')
def results():
return render_json(Results().get_results)

@app.route('/api/matches')
def matches():
return render_json(Matches().get_matches)
3 changes: 3 additions & 0 deletions conmebol/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from conmebol.classification import Classification
from conmebol.results import Results
from conmebol.matches import Matches
60 changes: 60 additions & 0 deletions conmebol/classification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import httpx
from bs4 import BeautifulSoup

from .models import Statistics
from .util import API_CLASSIFICATION
# from flags import participants

def _get_the_scores(soup: BeautifulSoup):
values = [value.text for value in soup]

if len(values) >= 2:
score_one, score_two = values[:2]
score_three = values[2] if len(values) > 2 else None

if score_three == None:
return score_one, score_two
return score_one, score_two, score_three

class Classification(object):
def __init__(self) -> None:
self.response = httpx.get(API_CLASSIFICATION, timeout=10.0)
self.response.raise_for_status()

def _get_statistics_country(self, soup: BeautifulSoup):
from dataclasses import asdict

_standing = soup.find_all('li', 'Standing_standings__rowLink__Skr86')
self.statistics = {'results': []}

for standing in _standing:
position = standing.find('div', 'Standing_standings__cell__5Kd0W').text
label = standing.find('div', 'Standing_standings__cellIcon__EbcOR').get('title')
country = standing.find('p', 'title-7-medium Standing_standings__teamName__psv61').text
# flag = participants[country]

matches_played, goal_difference = _get_the_scores(standing.find_all('div', 'Standing_standings__cell__5Kd0W Standing_standings__cellTextDimmed__vpZYH'))
won, tied, losses = _get_the_scores(standing.find_all('div', 'Standing_standings__cellLargeScreen__ttPap'))
points = standing.find('span', 'title-7-bold').text

data = Statistics(
# flag=flag,
country=country,
position=position,
label=label,
matches_played=matches_played,
won=won, tied=tied, losses=losses,
goal_difference=goal_difference,
points=points
)

self.statistics['results'].append(asdict(data))

@property
def get_positions(self):
soup = BeautifulSoup(self.response.content, "html.parser")
section_classification_country = soup.find('div', 'xpaLayoutContainerComponentResolver--standings')

self._get_statistics_country(section_classification_country)

return self.statistics
12 changes: 12 additions & 0 deletions conmebol/flags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
participants = {
"Argentina": "🇦🇷",
"Bolivia": "🇧🇴",
"Brasil": "🇧🇷",
"Colombia": "🇨🇴",
"Chile": "🇨🇱",
"Uruguay": "🇺🇾",
"Paraguay": "🇵🇾",
"Venezuela": "🇻🇪",
"Ecuador": "🇪🇨",
"Perú": "🇵🇪"
}
88 changes: 88 additions & 0 deletions conmebol/matches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import httpx
from bs4 import BeautifulSoup

from .util import API_MATCHES
from .models import NextMatches, LiveMatches
# from flags import participants

def get_match_prox_or_live(_journeys: list, matches: BeautifulSoup):
from dataclasses import asdict

team_names = [x.text for team in matches for x in team.find_all('span', 'SimpleMatchCardTeam_simpleMatchCardTeam__name__7Ud8D')]
goals = [x.text for team in matches for x in team.find_all('span', 'SimpleMatchCardTeam_simpleMatchCardTeam__score__UYMc_')]
match_dates = [x.find('time').get('datetime') if x.find('time') else x.text for team in matches for x in team.find_all('div', 'SimpleMatchCard_simpleMatchCard__matchContent__prwTf')]

team_counter = 0
date_index = 0
journey_index = 0
result_counter = 0
results = {}

for i in range(len(team_names)):
team_counter += 1

if team_counter == 10:
for j in range(i-9, i+1):
result_counter += 1

if result_counter == 2:
if all(g == '' for g in [goals[j-1], goals[j]]):
first_team = team_names[j-1]
second_team = team_names[j]
date = match_dates[date_index]

data = NextMatches(
first_team=first_team,
second_team=second_team,
date=date
)

if _journeys[journey_index] not in results:
results[_journeys[journey_index]] = []

results[_journeys[journey_index]].append(asdict(data))

date_index += 1
result_counter = 0
else:
first_team = {'country': team_names[j-1], 'goals': goals[j-1]}
second_team = {'country': team_names[j-1], 'goals': goals[j-1]}
winner = 'Tie' if goals[j-1] == goals[j] else team_names[j-1] if goals[j-1] > goals[j] else team_names[j]
time = match_dates[date_index]

data = LiveMatches(
first_team=first_team,
second_team=second_team,
winner=winner,
time=time
)

if _journeys[journey_index] not in results:
results[_journeys[journey_index]] = []

results[_journeys[journey_index]].append(asdict(data))

date_index += 1
team_counter = 0

journey_index += 1
team_counter = 0

return results

class Matches(object):
def __init__(self) -> None:
self.response = httpx.get(API_MATCHES, timeout=10.0)
self.response.raise_for_status()

@property
def get_matches(self):
soup = BeautifulSoup(self.response.content, "html.parser")
section_matches = soup.find('div', 'MatchCardsListsAppender_container__y5ame')

_journeys = [journey.text for journey in section_matches.find_all('div', 'SectionHeader_container__iVfZ9')]
_matches = section_matches.find_all('div', 'SimpleMatchCard_simpleMatchCard__content__ZWt2p')

self.results = get_match_prox_or_live(_journeys, _matches)

return self.results
34 changes: 34 additions & 0 deletions conmebol/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from dataclasses import dataclass

@dataclass
class Statistics:
# flag: str
country: str
position: int
label: str
matches_played: int
won: int
tied: int
losses: int
goal_difference: int
points: int

@dataclass
class LastMatches:
first_team: dict
second_team: dict
winner: str
date: str

@dataclass
class NextMatches:
first_team: str
second_team: str
date: str

@dataclass
class LiveMatches:
first_team: dict
second_team: dict
winner: str
time: str
69 changes: 69 additions & 0 deletions conmebol/results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import httpx
from bs4 import BeautifulSoup

from .models import LastMatches
from .util import API_RESULTS
# from flags import participants

def get_match_statistics(_journeys: list, matches: BeautifulSoup):
from dataclasses import asdict

team_names = [x.text for team in matches for x in team.find_all('span', 'SimpleMatchCardTeam_simpleMatchCardTeam__name__7Ud8D')]
goals = [x.text for team in matches for x in team.find_all('span', 'SimpleMatchCardTeam_simpleMatchCardTeam__score__UYMc_')]
match_dates = [x.find('time').get('datetime') for team in matches for x in team.find_all('div', 'SimpleMatchCard_simpleMatchCard__matchContent__prwTf')]

team_counter = 0
date_index = 0
journey_index = 0
result_counter = 0
results = {}

for i in range(len(team_names)):
team_counter += 1

if team_counter == 10:
for j in range(i-9, i+1):
result_counter += 1

if result_counter == 2:
first_team = {'country': team_names[j-1], 'goals': goals[j-1]}
second_team = {'country': team_names[j], 'goals': goals[j]}
winner = 'Tie' if goals[j-1] == goals[j] else team_names[j-1] if goals[j-1] > goals[j] else team_names[j]
date = match_dates[date_index]

data = LastMatches(
first_team=first_team,
second_team=second_team,
winner=winner,
date=date
)

if _journeys[journey_index] not in results:
results[_journeys[journey_index]] = []

results[_journeys[journey_index]].append(asdict(data))

date_index += 1
result_counter = 0

journey_index += 1
team_counter = 0

return results

class Results(object):
def __init__(self) -> None:
self.response = httpx.get(API_RESULTS, timeout=10.0)
self.response.raise_for_status()

@property
def get_results(self):
soup = BeautifulSoup(self.response.content, "html.parser")
section_journeys = soup.find('div', 'MatchCardsListsAppender_container__y5ame')

_journeys = [journey.text for journey in section_journeys.find_all('div', 'SectionHeader_container__iVfZ9')]
_matches = section_journeys.find_all('div', 'SimpleMatchCard_simpleMatchCard__content__ZWt2p')

self.results = get_match_statistics(_journeys, _matches)

return self.results
3 changes: 3 additions & 0 deletions conmebol/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
API_MATCHES='https://onefootball.com/es/competicion/conmebol-eliminatorias-copa-mundial-74/partidos'
API_RESULTS='https://onefootball.com/es/competicion/conmebol-eliminatorias-copa-mundial-74/resultados'
API_CLASSIFICATION='https://onefootball.com/es/competicion/conmebol-eliminatorias-copa-mundial-74/clasificacion'
11 changes: 11 additions & 0 deletions exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from flask import jsonify

def page_not_found(e):
message = jsonify({"error": "Sorry, the page you were looking for could not be found."})
message.status_code = 404
return message

def internal_server_error(e):
message = jsonify({"error": "Sorry, an internal problem has occurred on the server. Please try again later."})
message.status_code = 500
return message
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flask
flask_cors
httpx
bs4
Loading

0 comments on commit 5cc2f90

Please sign in to comment.