bep40 commited on
Commit
207b063
·
verified ·
1 Parent(s): a5907db

Fix: road_to_wc always gets og:image; hot_topics from 8 RSS sources (not just VnExpress)"

Browse files
Files changed (1) hide show
  1. wc2026_scraper.py +48 -32
wc2026_scraper.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- World Cup 2026 Data Module - NEWS ALWAYS HAS IMAGES (fetches og:image server-side)
3
  """
4
  import requests, re, time, threading
5
  from bs4 import BeautifulSoup
@@ -45,15 +45,13 @@ def _fetch(url, timeout=12):
45
  except: return ''
46
 
47
  def _get_og_image(url):
48
- """Fetch og:image from a URL — guaranteed to get thumbnail."""
49
  try:
50
- r = requests.get(url, headers=UA, timeout=8, allow_redirects=True)
51
  r.encoding = 'utf-8'
52
  if r.status_code != 200: return ''
53
- # Quick regex for og:image (faster than full parse)
54
- m = re.search(r'<meta[^>]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)["\']', r.text)
55
- if not m:
56
- m = re.search(r'<meta[^>]+content=["\']([^"\']+)["\'][^>]+property=["\']og:image["\']', r.text)
57
  if m:
58
  img = m.group(1)
59
  if img.startswith('//'): img = 'https:' + img
@@ -114,7 +112,6 @@ def scrape_standings():
114
  if not html: html = _bongda(f"/api/league-table/home?tournament_id={WC_ID}")
115
  r = {'html': html}; _set('wc_bxh', r); return r
116
 
117
- # ==================== STATS ====================
118
  def scrape_stats():
119
  c = _cached('wc_stats', 600)
120
  if c is not None: return c
@@ -127,13 +124,12 @@ def scrape_lineups(event_id): return {'html': _bongda(f"/api/fixtures/lineups?ev
127
  def scrape_match_detail(event_id): return {'html': _bongda(f"/api/fixtures/commentaries?event_id={event_id}")}
128
  def scrape_summary(): return scrape_standings()
129
 
130
- # ==================== NEWS (ALWAYS fetch og:image for each article) ====================
131
  def scrape_wc_news():
132
  c = _cached('wc_news', 300)
133
  if c is not None: return c
134
  news = []
135
-
136
- # VnExpress World Cup (best images)
137
  try:
138
  html = _fetch(f'https://timkiem.vnexpress.net/?q={quote("World Cup 2026")}')
139
  if html:
@@ -146,11 +142,9 @@ def scrape_wc_news():
146
  if not title or href in [n['link'] for n in news]: continue
147
  img_el = art.select_one('img')
148
  img = (img_el.get('data-src', '') or img_el.get('src', '')) if img_el else ''
149
- # Filter blank/placeholder images
150
  if img and ('blank' in img or 'data:image' in img): img = ''
151
  news.append({'title': title, 'link': href, 'img': img, 'source': 'VnExpress'})
152
  except: pass
153
-
154
  # Thanh Niên
155
  try:
156
  html = _fetch('https://worldcup2026.thanhnien.vn/')
@@ -167,7 +161,6 @@ def scrape_wc_news():
167
  news.append({'title': title, 'link': href, 'img': img, 'source': 'Thanh Niên'})
168
  if len(news) >= 20: break
169
  except: pass
170
-
171
  # Dân Trí
172
  try:
173
  html = _fetch('https://dantri.com.vn/the-thao/world-cup.htm')
@@ -180,7 +173,6 @@ def scrape_wc_news():
180
  if not title or len(title)<15 or href in [n['link'] for n in news]: continue
181
  news.append({'title': title, 'link': href, 'img': '', 'source': 'Dân Trí'})
182
  except: pass
183
-
184
  # TT&VH
185
  try:
186
  html = _fetch('https://thethaovanhoa.vn/rss/world-cup-2026.rss')
@@ -199,28 +191,25 @@ def scrape_wc_news():
199
  news.append({'title': title, 'link': link, 'img': img, 'source': 'TT&VH'})
200
  except: pass
201
 
202
- # === FETCH og:image for items without images (parallel, fast) ===
203
- def _fill_img(item):
204
- if item.get('img'): return item
205
- img = _get_og_image(item['link'])
206
- if img: item['img'] = img
207
- return item
208
-
209
- # Only fetch for first 15 items to keep response fast
210
- items_need_img = [n for n in news[:15] if not n.get('img')]
211
- if items_need_img:
212
- with ThreadPoolExecutor(5) as ex:
213
- futs = [ex.submit(_fill_img, item) for item in items_need_img[:10]]
214
- for f in as_completed(futs, timeout=12):
215
- try: f.result()
216
- except: pass
217
 
218
  _set('wc_news', news[:30]); return news[:30]
219
 
 
220
  def scrape_road_to_wc():
221
  c = _cached('wc_road', 600)
222
  if c is not None: return c
223
  articles = []
 
 
224
  for q in ['đường tới World Cup 2026', 'tuyển Việt Nam World Cup 2026']:
225
  try:
226
  html = _fetch(f'https://timkiem.vnexpress.net/?q={quote(q)}')
@@ -233,10 +222,37 @@ def scrape_road_to_wc():
233
  href = a.get('href', '')
234
  img_el = art.select_one('img')
235
  img = (img_el.get('data-src', '') or img_el.get('src', '')) if img_el else ''
236
- if not img: img = _get_og_image(href)
237
  if title and href and href not in [x['link'] for x in articles]:
238
  articles.append({'title': title, 'link': href, 'img': img, 'source': 'VnExpress', 'type': 'road'})
239
  except: continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  _set('wc_road', articles[:20]); return articles[:20]
241
 
242
  # ==================== ALL ====================
@@ -252,7 +268,7 @@ def get_wc2026_all():
252
  ex.submit(scrape_wc_news): 'news',
253
  ex.submit(scrape_road_to_wc): 'road',
254
  }
255
- for f in as_completed(futs, timeout=30):
256
  key = futs[f]
257
  try: data[key] = f.result()
258
  except: data[key] = {} if key in ('fixtures','standings','stats') else []
 
1
  """
2
+ World Cup 2026 Data Module - ALL NEWS ITEMS ALWAYS HAVE IMAGES
3
  """
4
  import requests, re, time, threading
5
  from bs4 import BeautifulSoup
 
45
  except: return ''
46
 
47
  def _get_og_image(url):
48
+ """Fetch og:image from URL."""
49
  try:
50
+ r = requests.get(url, headers=UA, timeout=10, allow_redirects=True)
51
  r.encoding = 'utf-8'
52
  if r.status_code != 200: return ''
53
+ m = re.search(r'<meta[^>]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)["\']', r.text[:5000])
54
+ if not m: m = re.search(r'<meta[^>]+content=["\']([^"\']+)["\'][^>]+property=["\']og:image["\']', r.text[:5000])
 
 
55
  if m:
56
  img = m.group(1)
57
  if img.startswith('//'): img = 'https:' + img
 
112
  if not html: html = _bongda(f"/api/league-table/home?tournament_id={WC_ID}")
113
  r = {'html': html}; _set('wc_bxh', r); return r
114
 
 
115
  def scrape_stats():
116
  c = _cached('wc_stats', 600)
117
  if c is not None: return c
 
124
  def scrape_match_detail(event_id): return {'html': _bongda(f"/api/fixtures/commentaries?event_id={event_id}")}
125
  def scrape_summary(): return scrape_standings()
126
 
127
+ # ==================== NEWS ====================
128
  def scrape_wc_news():
129
  c = _cached('wc_news', 300)
130
  if c is not None: return c
131
  news = []
132
+ # VnExpress
 
133
  try:
134
  html = _fetch(f'https://timkiem.vnexpress.net/?q={quote("World Cup 2026")}')
135
  if html:
 
142
  if not title or href in [n['link'] for n in news]: continue
143
  img_el = art.select_one('img')
144
  img = (img_el.get('data-src', '') or img_el.get('src', '')) if img_el else ''
 
145
  if img and ('blank' in img or 'data:image' in img): img = ''
146
  news.append({'title': title, 'link': href, 'img': img, 'source': 'VnExpress'})
147
  except: pass
 
148
  # Thanh Niên
149
  try:
150
  html = _fetch('https://worldcup2026.thanhnien.vn/')
 
161
  news.append({'title': title, 'link': href, 'img': img, 'source': 'Thanh Niên'})
162
  if len(news) >= 20: break
163
  except: pass
 
164
  # Dân Trí
165
  try:
166
  html = _fetch('https://dantri.com.vn/the-thao/world-cup.htm')
 
173
  if not title or len(title)<15 or href in [n['link'] for n in news]: continue
174
  news.append({'title': title, 'link': href, 'img': '', 'source': 'Dân Trí'})
175
  except: pass
 
176
  # TT&VH
177
  try:
178
  html = _fetch('https://thethaovanhoa.vn/rss/world-cup-2026.rss')
 
191
  news.append({'title': title, 'link': link, 'img': img, 'source': 'TT&VH'})
192
  except: pass
193
 
194
+ # FETCH og:image for ALL items without images
195
+ def _fill(item):
196
+ if item.get('img'): return
197
+ item['img'] = _get_og_image(item['link'])
198
+ with ThreadPoolExecutor(6) as ex:
199
+ futs = [ex.submit(_fill, n) for n in news[:20] if not n.get('img')]
200
+ for f in as_completed(futs, timeout=15):
201
+ try: f.result()
202
+ except: pass
 
 
 
 
 
 
203
 
204
  _set('wc_news', news[:30]); return news[:30]
205
 
206
+ # ==================== ROAD TO WC (also fetches og:image for ALL) ====================
207
  def scrape_road_to_wc():
208
  c = _cached('wc_road', 600)
209
  if c is not None: return c
210
  articles = []
211
+
212
+ # VnExpress
213
  for q in ['đường tới World Cup 2026', 'tuyển Việt Nam World Cup 2026']:
214
  try:
215
  html = _fetch(f'https://timkiem.vnexpress.net/?q={quote(q)}')
 
222
  href = a.get('href', '')
223
  img_el = art.select_one('img')
224
  img = (img_el.get('data-src', '') or img_el.get('src', '')) if img_el else ''
225
+ if img and ('blank' in img or 'data:image' in img): img = ''
226
  if title and href and href not in [x['link'] for x in articles]:
227
  articles.append({'title': title, 'link': href, 'img': img, 'source': 'VnExpress', 'type': 'road'})
228
  except: continue
229
+
230
+ # Thanh Niên road
231
+ try:
232
+ html = _fetch('https://worldcup2026.thanhnien.vn/')
233
+ if html:
234
+ soup = BeautifulSoup(html, 'lxml')
235
+ for a in soup.select('a[href*="duong-toi"], a[href*="vong-loai"], a[href*="tuyen-viet-nam"]')[:5]:
236
+ href = a.get('href', '')
237
+ title = _clean(a.get('title', '') or a.get_text())
238
+ if not title or len(title) < 15: continue
239
+ if not href.startswith('http'): href = 'https://worldcup2026.thanhnien.vn' + href
240
+ if href in [x['link'] for x in articles]: continue
241
+ img_el = a.select_one('img') or (a.parent.select_one('img') if a.parent else None)
242
+ img = (img_el.get('data-src','') or img_el.get('src','')) if img_el else ''
243
+ articles.append({'title': title, 'link': href, 'img': img, 'source': 'Thanh Niên', 'type': 'road'})
244
+ except: pass
245
+
246
+ # FETCH og:image for ALL without images
247
+ def _fill(item):
248
+ if item.get('img'): return
249
+ item['img'] = _get_og_image(item['link'])
250
+ with ThreadPoolExecutor(5) as ex:
251
+ futs = [ex.submit(_fill, a) for a in articles if not a.get('img')]
252
+ for f in as_completed(futs, timeout=15):
253
+ try: f.result()
254
+ except: pass
255
+
256
  _set('wc_road', articles[:20]); return articles[:20]
257
 
258
  # ==================== ALL ====================
 
268
  ex.submit(scrape_wc_news): 'news',
269
  ex.submit(scrape_road_to_wc): 'road',
270
  }
271
+ for f in as_completed(futs, timeout=35):
272
  key = futs[f]
273
  try: data[key] = f.result()
274
  except: data[key] = {} if key in ('fixtures','standings','stats') else []