bep40 commited on
Commit
ffc9f11
·
verified ·
1 Parent(s): 7373dd9

Upload match_detail.py

Browse files
Files changed (1) hide show
  1. match_detail.py +257 -0
match_detail.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Match Detail Scraper for bongda.com.vn
3
+ Fetches: preview/commentaries, lineups, head-to-head, stats for any match
4
+ """
5
+ import requests, re, json, time, threading
6
+ from bs4 import BeautifulSoup
7
+ from urllib.parse import quote
8
+
9
+ BONGDA_HEADERS = {
10
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
11
+ "Accept": "application/json, text/javascript, */*; q=0.01",
12
+ "Accept-Language": "vi-VN,vi;q=0.9",
13
+ "Referer": "https://bongda.com.vn/lich-thi-dau",
14
+ "X-Requested-With": "XMLHttpRequest"
15
+ }
16
+
17
+ UA = {
18
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
19
+ "Accept-Language": "vi-VN,vi;q=0.9"
20
+ }
21
+
22
+ _match_cache = {}
23
+ _match_cache_lock = threading.Lock()
24
+ _cache_ttl = 120 # 2 minutes for live matches
25
+
26
+
27
+ def _cached(key):
28
+ with _match_cache_lock:
29
+ if key in _match_cache and time.time() - _match_cache[key]['t'] < _cache_ttl:
30
+ return _match_cache[key]['d']
31
+ return None
32
+
33
+
34
+ def _set_cache(key, data):
35
+ with _match_cache_lock:
36
+ _match_cache[key] = {'t': time.time(), 'd': data}
37
+
38
+
39
+ def _bongda_api(endpoint, params=None):
40
+ """Call bongda.com.vn API and return HTML snippet."""
41
+ try:
42
+ url = f"https://bongda.com.vn{endpoint}"
43
+ if params:
44
+ url += "?" + "&".join(f"{k}={v}" for k, v in params.items())
45
+ r = requests.get(url, headers=BONGDA_HEADERS, timeout=10)
46
+ if r.status_code == 200:
47
+ data = r.json()
48
+ if data.get("status") == "success":
49
+ return data.get("html", "")
50
+ except:
51
+ pass
52
+ return ""
53
+
54
+
55
+ def _clean(s):
56
+ return re.sub(r'\s+', ' ', str(s or '')).strip()
57
+
58
+
59
+ def fetch_match_detail_by_url(url):
60
+ """
61
+ Extract event_id from a bongda.com.vn match URL like:
62
+ https://bongda.com.vn/tran-dau/5154126/preview/giao-huu-belgium-tunisia
63
+ Then fetch all match detail data.
64
+ """
65
+ # Extract match_id from URL
66
+ m = re.search(r'/tran-dau/(\d+)/', url)
67
+ if m:
68
+ event_id = int(m.group(1))
69
+ return fetch_match_detail(event_id)
70
+
71
+ # Try to fetch the page and extract event_id from embedded data
72
+ try:
73
+ r = requests.get(url, headers=UA, timeout=15)
74
+ if r.status_code == 200:
75
+ # Look for event_id in page source
76
+ m = re.search(r'event[_-]?id["\s:=]+(\d{5,})', r.text, re.I)
77
+ if m:
78
+ event_id = int(m.group(1))
79
+ return fetch_match_detail(event_id)
80
+ # Look in script tags
81
+ soup = BeautifulSoup(r.text, 'lxml')
82
+ for script in soup.find_all('script'):
83
+ txt = script.string or ''
84
+ m2 = re.search(r'event[_-]?id["\s:=]+(\d{5,})', txt, re.I)
85
+ if m2:
86
+ event_id = int(m2.group(1))
87
+ return fetch_match_detail(event_id)
88
+ except:
89
+ pass
90
+
91
+ return {"error": "Could not extract event_id from URL"}
92
+
93
+
94
+ def fetch_match_detail(event_id):
95
+ """
96
+ Fetch complete match detail:
97
+ - commentaries/preview
98
+ - lineups
99
+ - head-to-head
100
+ - match stats
101
+ Returns dict with HTML snippets + parsed preview text.
102
+ """
103
+ cache_key = f"match_detail_{event_id}"
104
+ cached = _cached(cache_key)
105
+ if cached:
106
+ return cached
107
+
108
+ result = {"event_id": event_id}
109
+
110
+ # 1. Commentaries / Preview
111
+ commentaries_html = _bongda_api("/api/fixtures/commentaries", {"event_id": event_id})
112
+ result["commentaries_html"] = commentaries_html
113
+
114
+ # Parse preview text from commentaries HTML
115
+ if commentaries_html:
116
+ try:
117
+ soup = BeautifulSoup(commentaries_html, 'lxml')
118
+ preview_text = []
119
+ for el in soup.find_all(['p', 'div', 'span']):
120
+ txt = el.get_text(strip=True)
121
+ if txt and len(txt) > 20:
122
+ preview_text.append(txt)
123
+ result["preview_text"] = preview_text
124
+ except:
125
+ result["preview_text"] = []
126
+ else:
127
+ result["preview_text"] = []
128
+
129
+ # 2. Lineups
130
+ lineups_html = _bongda_api("/api/fixtures/lineups", {"event_id": event_id})
131
+ result["lineups_html"] = lineups_html
132
+
133
+ # Parse lineups structured data
134
+ if lineups_html:
135
+ result["lineups"] = _parse_lineups(lineups_html)
136
+ else:
137
+ result["lineups"] = {}
138
+
139
+ # 3. Head-to-Head
140
+ h2h_html = _bongda_api("/api/fixtures/head-to-head", {"event_id": event_id})
141
+ result["h2h_html"] = h2h_html
142
+
143
+ # Parse H2H structured data
144
+ if h2h_html:
145
+ result["h2h"] = _parse_h2h(h2h_html)
146
+ else:
147
+ result["h2h"] = []
148
+
149
+ # 4. Match Stats
150
+ stats_html = _bongda_api("/api/event-standing/player-performance", {"event_id": event_id})
151
+ result["stats_html"] = stats_html
152
+
153
+ # 5. Match Info (basic info like referee, venue, date)
154
+ # This might be embedded in the commentaries or available via another endpoint
155
+ result["info"] = _parse_match_info(commentaries_html)
156
+
157
+ _set_cache(cache_key, result)
158
+ return result
159
+
160
+
161
+ def _parse_lineups(html):
162
+ """Parse lineups HTML into structured data."""
163
+ try:
164
+ soup = BeautifulSoup(html, 'lxml')
165
+ lineups = {"home_team": "", "away_team": "", "home_formation": "", "away_formation": "",
166
+ "home_players": [], "away_players": [], "home_subs": [], "away_subs": []}
167
+
168
+ # Find team names
169
+ team_headers = soup.select('.team-name, .team-header, [class*=team] h3, [class*=team] h4')
170
+ if len(team_headers) >= 2:
171
+ lineups["home_team"] = _clean(team_headers[0].get_text())
172
+ lineups["away_team"] = _clean(team_headers[1].get_text())
173
+
174
+ # Find formations
175
+ formations = soup.select('.formation, [class*=formation]')
176
+ if len(formations) >= 2:
177
+ lineups["home_formation"] = _clean(formations[0].get_text())
178
+ lineups["away_formation"] = _clean(formations[1].get_text())
179
+
180
+ # Find starting XI players - look for player rows/items
181
+ home_section = soup.select_one('.home-team, .team-home, [class*=home]')
182
+ away_section = soup.select_one('.away-team, .team-away, [class*=away]')
183
+
184
+ if home_section:
185
+ for player_el in home_section.select('.player, .player-item, .player-name, tr, li'):
186
+ name = _clean(player_el.get_text())
187
+ if name and len(name) > 2 and len(name) < 60:
188
+ lineups["home_players"].append(name)
189
+
190
+ if away_section:
191
+ for player_el in away_section.select('.player, .player-item, .player-name, tr, li'):
192
+ name = _clean(player_el.get_text())
193
+ if name and len(name) > 2 and len(name) < 60:
194
+ lineups["away_players"].append(name)
195
+
196
+ # Remove duplicates while preserving order
197
+ lineups["home_players"] = list(dict.fromkeys(lineups["home_players"]))
198
+ lineups["away_players"] = list(dict.fromkeys(lineups["away_players"]))
199
+
200
+ return lineups
201
+ except:
202
+ return {"home_team": "", "away_team": "", "home_players": [], "away_players": [],
203
+ "home_subs": [], "away_subs": []}
204
+
205
+
206
+ def _parse_h2h(html):
207
+ """Parse head-to-head HTML into list of previous matches."""
208
+ try:
209
+ soup = BeautifulSoup(html, 'lxml')
210
+ matches = []
211
+
212
+ for row in soup.select('tr, .match-item, [class*=match], li'):
213
+ cells = row.find_all(['td', 'div', 'span'])
214
+ if len(cells) >= 3:
215
+ date = _clean(cells[0].get_text())
216
+ home = _clean(cells[1].get_text())
217
+ away = _clean(cells[2].get_text()) if len(cells) > 2 else ""
218
+ score = _clean(cells[3].get_text()) if len(cells) > 3 else ""
219
+ if date and home and len(date) > 3:
220
+ matches.append({
221
+ "date": date,
222
+ "home": home,
223
+ "away": away,
224
+ "score": score
225
+ })
226
+
227
+ return matches
228
+ except:
229
+ return []
230
+
231
+
232
+ def _parse_match_info(html):
233
+ """Parse basic match info from commentaries HTML."""
234
+ info = {}
235
+ try:
236
+ soup = BeautifulSoup(html, 'lxml')
237
+ text = soup.get_text(' ')
238
+
239
+ # Look for common patterns
240
+ date_m = re.search(r'(\d{1,2}[/-]\d{1,2}[/-]\d{2,4}|\d{4}[/-]\d{1,2}[/-]\d{1,2})', text)
241
+ if date_m:
242
+ info["date"] = date_m.group(1)
243
+
244
+ time_m = re.search(r'(\d{1,2}:\d{2})', text)
245
+ if time_m:
246
+ info["time"] = time_m.group(1)
247
+
248
+ stadium_m = re.search(r'(Sân vận động|SVĐ|Stadium|Nhà thi đấu)[:\s]+([^,\n]+)', text, re.I)
249
+ if stadium_m:
250
+ info["stadium"] = _clean(stadium_m.group(2))
251
+
252
+ referee_m = re.search(r'(Trọng tài|Referee)[:\s]+([^,\n]+)', text, re.I)
253
+ if referee_m:
254
+ info["referee"] = _clean(referee_m.group(2))
255
+ except:
256
+ pass
257
+ return info