bep40 commited on
Commit
dc3fcd2
·
verified ·
1 Parent(s): 080896b

Update wc2026_scraper.py

Browse files
Files changed (1) hide show
  1. wc2026_scraper.py +30 -11
wc2026_scraper.py CHANGED
@@ -1,5 +1,6 @@
1
  """
2
  World Cup 2026 - NEWS uses same 8-source search as hashtag (NOT Google News)
 
3
  """
4
  import requests, re, time, threading
5
  from bs4 import BeautifulSoup
@@ -9,14 +10,21 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
9
 
10
  BONGDA_HEADERS = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36","Accept":"application/json, text/javascript, */*; q=0.01","Accept-Language":"vi-VN,vi;q=0.9","Referer":"https://bongda.com.vn/giai-dau/24254/standings/world-cup","X-Requested-With":"XMLHttpRequest"}
11
  UA = {'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','Accept-Language':'vi-VN,vi;q=0.9'}
12
- WC_ID=24254;VN_TZ=timezone(timedelta(hours=7));CACHE={};LOCK=threading.Lock()
 
 
 
 
13
  def _clean(s):return re.sub(r'\s+',' ',str(s or'')).strip()
 
14
  def _cached(key,ttl=120):
15
  with LOCK:
16
  if key in CACHE and time.time()-CACHE[key]['t']<ttl:return CACHE[key]['d']
17
  return None
 
18
  def _set(key,data):
19
  with LOCK:CACHE[key]={'t':time.time(),'d':data}
 
20
  def _bongda(endpoint):
21
  try:
22
  r=requests.get(f"https://bongda.com.vn{endpoint}",headers=BONGDA_HEADERS,timeout=10)
@@ -25,9 +33,11 @@ def _bongda(endpoint):
25
  if data.get("status")=="success":return data.get("html","")
26
  except:pass
27
  return""
 
28
  def _fetch(url,timeout=12):
29
  try:r=requests.get(url,headers=UA,timeout=timeout,allow_redirects=True);r.encoding='utf-8';return r.text if r.status_code==200 else''
30
  except:return''
 
31
  def _get_og_image(url):
32
  try:
33
  r=requests.get(url,headers=UA,timeout=8,allow_redirects=True);r.encoding='utf-8'
@@ -37,7 +47,9 @@ def _get_og_image(url):
37
  if m:img=m.group(1);return('https:'+img if img.startswith('//') else img)
38
  except:pass
39
  return''
 
40
  def _utc_to_vn(date_str):
 
41
  try:
42
  dt_str=date_str.replace('Z','+00:00')
43
  if '+' not in dt_str and 'T' in dt_str:dt_str+='+00:00'
@@ -62,20 +74,26 @@ def scrape_fixtures():
62
  except:match['status']='upcoming'
63
  matches.append(match)
64
  except:pass
65
- r={'matches':matches,'total':len(matches)};_set('wc_fix',r);return r
 
 
66
 
67
  def scrape_standings():
68
  c=_cached('wc_bxh',180)
69
  if c is not None:return c
70
  html=_bongda(f"/api/league-table/home?tournament_id={WC_ID}&is_detail=True")
71
  if not html:html=_bongda(f"/api/league-table/home?tournament_id={WC_ID}")
72
- r={'html':html};_set('wc_bxh',r);return r
 
 
73
 
74
  def scrape_stats():
75
  c=_cached('wc_stats',600)
76
  if c is not None:return c
77
  html=_bongda(f"/api/event-standing/player-performance?tournament_id={WC_ID}")
78
- r={'html':html};_set('wc_stats',r);return r
 
 
79
 
80
  def scrape_history():return scrape_standings()
81
  def scrape_h2h(event_id):return{'html':_bongda(f"/api/fixtures/head-to-head?event_id={event_id}")}
@@ -83,7 +101,6 @@ def scrape_lineups(event_id):return{'html':_bongda(f"/api/fixtures/lineups?event
83
  def scrape_match_detail(event_id):return{'html':_bongda(f"/api/fixtures/commentaries?event_id={event_id}")}
84
  def scrape_summary():return scrape_standings()
85
 
86
- # === NEWS: uses SAME 8-source search as hashtag (NOT Google News) ===
87
  def _search_source(url_tpl, topic, selector, base_url='', limit=6):
88
  """Generic search on a VN news site."""
89
  items=[]
@@ -122,7 +139,6 @@ def scrape_wc_news():
122
  url_tpl,selector,base,source=cfg
123
  items=_search_source(url_tpl,topic,selector,base,8)
124
  return [(item,source) for item in items]
125
- # bongda.com.vn + bongdaplus.vn via Jina (JS-protected) — keyword-filter for World Cup
126
  def _fetch_bongda_jina():
127
  out=[]
128
  kws=['world cup','wc 2026','world cup 2026','tuyển','đội tuyển','vòng loại','fifa']
@@ -147,12 +163,10 @@ def scrape_wc_news():
147
  all_news.append({'title':item['title'],'link':item['link'],'img':'','source':source})
148
  except:pass
149
 
150
- # Deduplicate
151
  seen=set();unique=[]
152
  for n in all_news:
153
  if n['link'] not in seen:seen.add(n['link']);unique.append(n)
154
 
155
- # Fetch og:image for first 12
156
  def _fill(item):
157
  if not item.get('img'):item['img']=_get_og_image(item['link'])
158
  with ThreadPoolExecutor(6) as ex:
@@ -161,7 +175,8 @@ def scrape_wc_news():
161
  try:f.result()
162
  except:pass
163
 
164
- _set('wc_news',unique[:30]);return unique[:30]
 
165
 
166
  def scrape_road_to_wc():
167
  """Road to WC also uses direct search (not Google News)."""
@@ -174,7 +189,8 @@ def scrape_road_to_wc():
174
  if item['link'] not in[x['link'] for x in articles]:
175
  img=_get_og_image(item['link'])
176
  articles.append({'title':item['title'],'link':item['link'],'img':img,'source':'VnExpress','type':'road'})
177
- _set('wc_road',articles[:20]);return articles[:20]
 
178
 
179
  def get_wc2026_all():
180
  c=_cached('wc_all',90)
@@ -186,4 +202,7 @@ def get_wc2026_all():
186
  key=futs[f]
187
  try:data[key]=f.result()
188
  except:data[key]={} if key in('fixtures','standings','stats') else[]
189
- data['summary']=data.get('standings',{});_set('wc_all',data);return data
 
 
 
 
1
  """
2
  World Cup 2026 - NEWS uses same 8-source search as hashtag (NOT Google News)
3
+ Timezone: UTC+7 (Asia/Ho_Chi_Minh)
4
  """
5
  import requests, re, time, threading
6
  from bs4 import BeautifulSoup
 
10
 
11
  BONGDA_HEADERS = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36","Accept":"application/json, text/javascript, */*; q=0.01","Accept-Language":"vi-VN,vi;q=0.9","Referer":"https://bongda.com.vn/giai-dau/24254/standings/world-cup","X-Requested-With":"XMLHttpRequest"}
12
  UA = {'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','Accept-Language':'vi-VN,vi;q=0.9'}
13
+ WC_ID=24254
14
+ VN_TZ=timezone(timedelta(hours=7))
15
+ CACHE={}
16
+ LOCK=threading.Lock()
17
+
18
  def _clean(s):return re.sub(r'\s+',' ',str(s or'')).strip()
19
+
20
  def _cached(key,ttl=120):
21
  with LOCK:
22
  if key in CACHE and time.time()-CACHE[key]['t']<ttl:return CACHE[key]['d']
23
  return None
24
+
25
  def _set(key,data):
26
  with LOCK:CACHE[key]={'t':time.time(),'d':data}
27
+
28
  def _bongda(endpoint):
29
  try:
30
  r=requests.get(f"https://bongda.com.vn{endpoint}",headers=BONGDA_HEADERS,timeout=10)
 
33
  if data.get("status")=="success":return data.get("html","")
34
  except:pass
35
  return""
36
+
37
  def _fetch(url,timeout=12):
38
  try:r=requests.get(url,headers=UA,timeout=timeout,allow_redirects=True);r.encoding='utf-8';return r.text if r.status_code==200 else''
39
  except:return''
40
+
41
  def _get_og_image(url):
42
  try:
43
  r=requests.get(url,headers=UA,timeout=8,allow_redirects=True);r.encoding='utf-8'
 
47
  if m:img=m.group(1);return('https:'+img if img.startswith('//') else img)
48
  except:pass
49
  return''
50
+
51
  def _utc_to_vn(date_str):
52
+ """Convert UTC date string to VN timezone (UTC+7)"""
53
  try:
54
  dt_str=date_str.replace('Z','+00:00')
55
  if '+' not in dt_str and 'T' in dt_str:dt_str+='+00:00'
 
74
  except:match['status']='upcoming'
75
  matches.append(match)
76
  except:pass
77
+ r={'matches':matches,'total':len(matches),'timezone':'UTC+7'}
78
+ _set('wc_fix',r)
79
+ return r
80
 
81
  def scrape_standings():
82
  c=_cached('wc_bxh',180)
83
  if c is not None:return c
84
  html=_bongda(f"/api/league-table/home?tournament_id={WC_ID}&is_detail=True")
85
  if not html:html=_bongda(f"/api/league-table/home?tournament_id={WC_ID}")
86
+ r={'html':html}
87
+ _set('wc_bxh',r)
88
+ return r
89
 
90
  def scrape_stats():
91
  c=_cached('wc_stats',600)
92
  if c is not None:return c
93
  html=_bongda(f"/api/event-standing/player-performance?tournament_id={WC_ID}")
94
+ r={'html':html}
95
+ _set('wc_stats',r)
96
+ return r
97
 
98
  def scrape_history():return scrape_standings()
99
  def scrape_h2h(event_id):return{'html':_bongda(f"/api/fixtures/head-to-head?event_id={event_id}")}
 
101
  def scrape_match_detail(event_id):return{'html':_bongda(f"/api/fixtures/commentaries?event_id={event_id}")}
102
  def scrape_summary():return scrape_standings()
103
 
 
104
  def _search_source(url_tpl, topic, selector, base_url='', limit=6):
105
  """Generic search on a VN news site."""
106
  items=[]
 
139
  url_tpl,selector,base,source=cfg
140
  items=_search_source(url_tpl,topic,selector,base,8)
141
  return [(item,source) for item in items]
 
142
  def _fetch_bongda_jina():
143
  out=[]
144
  kws=['world cup','wc 2026','world cup 2026','tuyển','đội tuyển','vòng loại','fifa']
 
163
  all_news.append({'title':item['title'],'link':item['link'],'img':'','source':source})
164
  except:pass
165
 
 
166
  seen=set();unique=[]
167
  for n in all_news:
168
  if n['link'] not in seen:seen.add(n['link']);unique.append(n)
169
 
 
170
  def _fill(item):
171
  if not item.get('img'):item['img']=_get_og_image(item['link'])
172
  with ThreadPoolExecutor(6) as ex:
 
175
  try:f.result()
176
  except:pass
177
 
178
+ _set('wc_news',unique[:30])
179
+ return unique[:30]
180
 
181
  def scrape_road_to_wc():
182
  """Road to WC also uses direct search (not Google News)."""
 
189
  if item['link'] not in[x['link'] for x in articles]:
190
  img=_get_og_image(item['link'])
191
  articles.append({'title':item['title'],'link':item['link'],'img':img,'source':'VnExpress','type':'road'})
192
+ _set('wc_road',articles[:20])
193
+ return articles[:20]
194
 
195
  def get_wc2026_all():
196
  c=_cached('wc_all',90)
 
202
  key=futs[f]
203
  try:data[key]=f.result()
204
  except:data[key]={} if key in('fixtures','standings','stats') else[]
205
+ data['summary']=data.get('standings',{})
206
+ data['timezone']='UTC+7'
207
+ _set('wc_all',data)
208
+ return data