bep40 commited on
Commit
7e9652f
·
verified ·
1 Parent(s): c476b7c

v6.3: MyTV scraper cải tiến — dùng URL pattern từ VNEWS matches + verify URL

Browse files
Files changed (1) hide show
  1. main.py +91 -0
main.py CHANGED
@@ -430,6 +430,97 @@ async def api_short_action(request:Request):
430
  out={"views":int(st.get("views",0)),"likes":int(st.get("likes",0)),"shares":int(st.get("shares",0)),"comments":st.get("comments",[])[:80]}
431
  return JSONResponse({"stats":out})
432
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
433
  @app.get("/api/highlights")
434
  def api_highlights():return JSONResponse(_cached("xemlaibongda_hl",scrape_xemlaibongda,ttl=_cache_ttl))
435
  @app.get("/api/highlights/leagues")
 
430
  out={"views":int(st.get("views",0)),"likes":int(st.get("likes",0)),"shares":int(st.get("shares",0)),"comments":st.get("comments",[])[:80]}
431
  return JSONResponse({"stats":out})
432
 
433
+ # ===== MyTV.com.vn Video Scraper =====
434
+ import unicodedata as _ud
435
+
436
+ def _slugify_mytv(s):
437
+ """Tạo slug cho mytv URL từ tên đội"""
438
+ s = _ud.normalize('NFKD', s)
439
+ s = ''.join(c for c in s if not _ud.combining(c))
440
+ s = s.lower().replace(" ", "-")
441
+ s = re.sub(r'[^a-z0-9-]', '', s)
442
+ return s
443
+
444
+ def scrape_mytv_highlights(limit=20):
445
+ """Lấy video từ mytv.com.vn cho các trận đấu đã kết thúc.
446
+ Vì mytv là Next.js SPA nên không scrape trực tiếp được.
447
+ Thay vào đó, dùng URL pattern từ các trận đấu trong VNEWS.
448
+ """
449
+ videos = []
450
+ seen_urls = set()
451
+
452
+ try:
453
+ # Lấy trận đấu đã kết thúc trong 7 ngày qua
454
+ from datetime import datetime as _dt, timedelta as _td
455
+ now = _dt.now()
456
+
457
+ for day_offset in range(7):
458
+ day = now - _td(days=day_offset)
459
+ ds = day.strftime("%Y-%m-%d")
460
+ try:
461
+ day_data = fetch_bongda_api(f"/api/fixtures/get-by-date?date={ds}")
462
+ if not day_data or len(day_data) < 100:
463
+ continue
464
+ soup = BeautifulSoup(day_data, 'lxml')
465
+ for li in soup.select("li.match-detail"):
466
+ status_el = li.select_one('.status .label')
467
+ status_text = status_el.get_text(strip=True) if status_el else ""
468
+ # Chỉ lấy trận đã kết thúc
469
+ if status_text not in ('KT', 'FT', 'Finished'):
470
+ continue
471
+
472
+ home_el = li.select_one('.home-team .name')
473
+ away_el = li.select_one('.away-team .name')
474
+ home = home_el.get_text(strip=True) if home_el else ""
475
+ away = away_el.get_text(strip=True) if away_el else ""
476
+
477
+ if not home or not away:
478
+ continue
479
+
480
+ # Build mytv URL
481
+ home_s = _slugify_mytv(home)
482
+ away_s = _slugify_mytv(away)
483
+ mytv_url = f"https://mytv.com.vn/{home_s}-vs-{away_s}"
484
+
485
+ if mytv_url in seen_urls:
486
+ continue
487
+ seen_urls.add(mytv_url)
488
+
489
+ # Verify URL exists (HEAD request, non-blocking)
490
+ try:
491
+ head_r = requests.head(mytv_url, headers=HEADERS, timeout=5, allow_redirects=True)
492
+ if head_r.status_code == 200 and "anonymous" not in head_r.url:
493
+ videos.append({
494
+ "title": f"{home} vs {away}",
495
+ "url": mytv_url,
496
+ "source": "mytv",
497
+ "type": "full_match",
498
+ "home": home,
499
+ "away": away,
500
+ "date": ds
501
+ })
502
+ except:
503
+ pass
504
+
505
+ if len(videos) >= limit:
506
+ break
507
+ except:
508
+ pass
509
+
510
+ if len(videos) >= limit:
511
+ break
512
+ except:
513
+ pass
514
+
515
+ return videos
516
+
517
+ @app.get("/api/mytv/videos")
518
+ def api_mytv_videos(limit: int = Query(default=20, le=50)):
519
+ """Lấy danh sách video từ mytv.com.vn"""
520
+ def _f():
521
+ return scrape_mytv_highlights(limit=limit)
522
+ return JSONResponse(_cached("mytv_videos", _f, ttl=300))
523
+
524
  @app.get("/api/highlights")
525
  def api_highlights():return JSONResponse(_cached("xemlaibongda_hl",scrape_xemlaibongda,ttl=_cache_ttl))
526
  @app.get("/api/highlights/leagues")