VNEWS / bongda_proxy.py
bep40's picture
Upload bongda_proxy.py with huggingface_hub
86310c3 verified
Raw
History Blame
10.1 kB
"""VNEWS — Bongda Proxy Endpoint (for fast match detail loading)"""
import requests
from bs4 import BeautifulSoup
import re
import json
# Import-safe parsing functions
def _cl(s):
return re.sub(r'\s+', ' ', str(s or '')).strip()
def _normalize_time(raw):
t = _cl(raw)
t = re.sub(r"(\d+)'\s*\+(\d+)", r"\1+\2'", t)
t = t.replace("''", "'")
return t
def scrape_match_html(event_id, url=None):
"""Fast scrape bongda.com.vn for match detail - no external CORS needed."""
result = {"event_id": event_id, "found": False, "sections": []}
# Fetch HTML
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Referer": "https://bongda.com.vn/",
}
html = None
urls_to_try = [url] if url else []
urls_to_try += [
f"https://bongda.com.vn/tran-dau/{event_id}/centre/",
f"https://bongda.com.vn/tran-dau/{event_id}/preview/",
]
for u in urls_to_try:
if not u:
continue
try:
resp = requests.get(u, headers=headers, timeout=15, allow_redirects=True)
if resp.status_code == 200 and len(resp.text) > 1000:
html = resp.text
break
except Exception:
continue
if not html:
return result
try:
soup = BeautifulSoup(html, 'html.parser')
info = {}
# Teams + score
tel = soup.select_one('.teams')
if tel:
he = tel.select_one('.team.home')
if he:
ne = he.select_one('p:not(.logo)') or he.find('p')
if ne:
info['home_team'] = _cl(ne.get_text())
lo = he.select_one('img')
if lo:
info['home_logo'] = lo.get('src', '')
ae = tel.select_one('.team.away')
if ae:
ne = ae.select_one('p:not(.logo)') or ae.find('p')
if ne:
info['away_team'] = _cl(ne.get_text())
lo = ae.select_one('img')
if lo:
info['away_logo'] = lo.get('src', '')
sc = tel.select_one('.score')
if sc:
parts = [_cl(p.get_text()) for p in sc.select('p')]
if len(parts) >= 2:
info['score'] = f"{parts[0]} - {parts[1]}"
lb = sc.select_one('.label')
if lb:
info['status_label'] = _cl(lb.get_text())
if info.get('home_team') and info.get('away_team'):
result['info'] = info
result['found'] = True
result['sections'].append('info')
else:
return result
# Events parsing
events = []
events_div = soup.select_one('.events')
if events_div:
period = ''
for child in events_div.children:
if not hasattr(child, 'name') or not child.name:
continue
cls = ' '.join(child.get('class', []))
if 'period' in cls:
h2 = child.find('h2')
if h2:
period = _cl(h2.get_text())
for ev in child.children:
if not hasattr(ev, 'name') or not ev.name:
continue
ev_cls = ' '.join(ev.get('class', []))
if 'event' not in ev_cls:
continue
ev_data = {
'team': 'home' if 'home' in ev_cls else 'away',
'period': period,
'type': 'unknown',
'time': '',
}
# Type detection
type_el = ev.select_one('.event-type')
if type_el:
if type_el.select_one('[class*="redcard"]'):
ev_data['type'] = 'redcard'
elif type_el.select_one('[class*="yellowcard"]'):
ev_data['type'] = 'yellowcard'
elif type_el.select_one('[class*="goal"]'):
ev_data['type'] = 'goal'
elif type_el.select_one('[class*="substitution"]'):
ev_data['type'] = 'substitution'
players_el = ev.select_one('.players')
if players_el:
time_el = players_el.select_one('.event-time')
if time_el:
ev_data['time'] = _normalize_time(time_el.get_text())
# Parse player names
text = _cl(players_el.get_text(' ', strip=True).replace(ev_data['time'], '').strip())
ev_data['players'] = text
if ev_data['type'] == 'goal':
words = text.split()
if len(words) >= 2:
ev_data['scorer'] = ' '.join(words[:2])
elif len(words) == 1:
ev_data['scorer'] = words[0]
elif ev_data['type'] == 'substitution':
words = text.split()
if len(words) >= 4:
ev_data['player_out'] = ' '.join(words[:len(words)//2])
ev_data['player_in'] = ' '.join(words[len(words)//2:])
elif ev_data['type'] in ('redcard', 'yellowcard'):
ev_data['player'] = text
events.append(ev_data)
if events:
result['events'] = events
result['sections'].append('events')
# Prediction
pred = soup.select_one('.prediction-card')
if pred:
pred_data = {}
team_info = pred.select_one('.team-info')
if team_info:
teams = team_info.select('.team')
if len(teams) >= 2:
pred_data['home_name'] = _cl(teams[0].select_one('.team-name').get_text() or '') if teams[0].select_one('.team-name') else ''
pred_data['away_name'] = _cl(teams[1].select_one('.team-name').get_text() or '') if teams[1].select_one('.team-name') else ''
divider = team_info.select_one('.divider')
if divider:
pred_data['result'] = _cl(divider.get_text())
vc = pred.select_one('.vote-count')
if vc:
pred_data['vote_count'] = _cl(vc.get_text())
result['prediction'] = pred_data
# Recent matches
recent = []
ml = soup.select_one('.matches-list')
if ml:
for item in ml.select('.match-detail, .match-item, li'):
de = item.select_one('.date, .time')
le = item.select_one('.league')
he = item.select_one('.home, .team-home')
ae = item.select_one('.away, .team-away')
se = item.select_one('.score, .result')
if he or ae:
recent.append({
'date': _cl(de.get_text()) if de else '',
'league': _cl(le.get_text()) if le else '',
'home': _cl(he.get_text()) if he else '',
'away': _cl(ae.get_text()) if ae else '',
'score': _cl(se.get_text()) if se else 'vs',
})
if recent:
result['recent_matches'] = recent
result['sections'].append('recent')
# H2H stats API
try:
api_headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
"Referer": "https://bongda.com.vn/",
}
r = requests.get(
f"https://bongda.com.vn/api/fixtures/h2h-stats?event_id={event_id}",
headers=api_headers, timeout=10
)
if r.status_code == 200:
ad = r.json()
if ad.get('status') == 'success' and ad.get('html'):
asp = BeautifulSoup(ad['html'], 'html.parser')
ast = {}
for row in asp.select('li, tr'):
cells = row.select('td, span, p')
if len(cells) >= 3:
lb = _cl(cells[0].get_text())
if lb:
ast[lb] = {'home': _cl(cells[1].get_text()), 'away': _cl(cells[2].get_text())}
if ast:
result['h2h_stats_parsed'] = ast
result['sections'].append('h2h_stats')
except Exception:
pass
except Exception as e:
result['error'] = str(e)
return result
# FastAPI endpoint (to be added to app_v2_entry.py)
from fastapi import Query
from fastapi.responses import JSONResponse
def add_bongda_proxy_endpoint(app):
@app.get('/api/proxy/bongda')
def proxy_bongda(event_id: int = Query(default=None), url: str = Query(default=None)):
"""Proxy bongda.com.vn match data - fast server-side scraping."""
if event_id is None:
return JSONResponse({'error': 'event_id required'}, status_code=400)
return JSONResponse(scrape_match_html(event_id, url))