Spaces:
Running
Running
feat: add _extract_24h_video_url to shorts_carousel.py
Browse files- shorts_carousel.py +25 -10
shorts_carousel.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Self-contained 24h Shorts Carousel
|
| 2 |
import re
|
| 3 |
import requests
|
| 4 |
from bs4 import BeautifulSoup
|
|
@@ -12,7 +12,7 @@ BASE_24H = "https://www.24h.com.vn"
|
|
| 12 |
|
| 13 |
def scrape_24h_news_shorts():
|
| 14 |
"""Scrape https://www.24h.com.vn/video/video-tin-tuc-cvd769.html
|
| 15 |
-
Returns only ACTUAL article links (pattern: -c{N}a{N}.html)
|
| 16 |
try:
|
| 17 |
url = f"{BASE_24H}/video/video-tin-tuc-cvd769.html"
|
| 18 |
r = requests.get(url, headers=HEADERS, timeout=15)
|
|
@@ -21,7 +21,6 @@ def scrape_24h_news_shorts():
|
|
| 21 |
articles, seen = [], set()
|
| 22 |
for a in soup.find_all("a", href=True):
|
| 23 |
href = a.get("href", "")
|
| 24 |
-
# Only match real article URLs: ends with -c{digits}a{digits}.html
|
| 25 |
if not re.search(r'-c\d+a\d+\.html$', href):
|
| 26 |
continue
|
| 27 |
if not href.startswith("http"):
|
|
@@ -29,7 +28,6 @@ def scrape_24h_news_shorts():
|
|
| 29 |
if href in seen:
|
| 30 |
continue
|
| 31 |
seen.add(href)
|
| 32 |
-
# Get title
|
| 33 |
title = a.get("title", "") or ""
|
| 34 |
img = a.find("img")
|
| 35 |
if img and not title:
|
|
@@ -38,13 +36,11 @@ def scrape_24h_news_shorts():
|
|
| 38 |
title = a.get_text(strip=True)
|
| 39 |
if not title or len(title) < 10:
|
| 40 |
continue
|
| 41 |
-
# Get image - priority: data-original > data-src > src
|
| 42 |
img_src = None
|
| 43 |
if img:
|
| 44 |
img_src = img.get("data-original") or img.get("data-src") or img.get("src") or ""
|
| 45 |
if "base64" in img_src or len(img_src) < 20:
|
| 46 |
img_src = None
|
| 47 |
-
# Try parent element for image if not found in link
|
| 48 |
if not img_src:
|
| 49 |
parent = a.parent
|
| 50 |
if parent:
|
|
@@ -54,7 +50,7 @@ def scrape_24h_news_shorts():
|
|
| 54 |
if "base64" in img_src or len(img_src) < 20:
|
| 55 |
img_src = None
|
| 56 |
if not img_src:
|
| 57 |
-
continue
|
| 58 |
articles.append({
|
| 59 |
"title": title, "link": href, "img": img_src,
|
| 60 |
"source": "24h", "is_video": True
|
|
@@ -64,6 +60,26 @@ def scrape_24h_news_shorts():
|
|
| 64 |
return []
|
| 65 |
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
# CSS for the 9:16 shorts slider
|
| 68 |
SHORTS_CSS = """
|
| 69 |
/* Shorts 9:16 Slider */
|
|
@@ -78,8 +94,7 @@ SHORTS_CSS = """
|
|
| 78 |
|
| 79 |
def render_shorts_carousel(shorts, esc_fn, safe_url_fn, make_id_fn, slug_fn):
|
| 80 |
"""Render 9:16 vertical shorts carousel HTML.
|
| 81 |
-
Click opens the article
|
| 82 |
-
"""
|
| 83 |
vids = [v for v in shorts if v.get("img")]
|
| 84 |
if not vids:
|
| 85 |
return ""
|
|
@@ -90,7 +105,7 @@ def render_shorts_carousel(shorts, esc_fn, safe_url_fn, make_id_fn, slug_fn):
|
|
| 90 |
title = v.get("title", "")
|
| 91 |
aid = make_id_fn(link)
|
| 92 |
sl = slug_fn(title)
|
| 93 |
-
#
|
| 94 |
cjs = "window.bdpOpen('" + esc_fn(link) + "','" + aid + "','" + sl + "')"
|
| 95 |
items.append(
|
| 96 |
'<div class="vslide-shorts-item" onclick="' + cjs + '">'
|
|
|
|
| 1 |
+
"""Self-contained 24h Shorts Carousel + video extraction."""
|
| 2 |
import re
|
| 3 |
import requests
|
| 4 |
from bs4 import BeautifulSoup
|
|
|
|
| 12 |
|
| 13 |
def scrape_24h_news_shorts():
|
| 14 |
"""Scrape https://www.24h.com.vn/video/video-tin-tuc-cvd769.html
|
| 15 |
+
Returns only ACTUAL article links (pattern: -c{N}a{N}.html)."""
|
| 16 |
try:
|
| 17 |
url = f"{BASE_24H}/video/video-tin-tuc-cvd769.html"
|
| 18 |
r = requests.get(url, headers=HEADERS, timeout=15)
|
|
|
|
| 21 |
articles, seen = [], set()
|
| 22 |
for a in soup.find_all("a", href=True):
|
| 23 |
href = a.get("href", "")
|
|
|
|
| 24 |
if not re.search(r'-c\d+a\d+\.html$', href):
|
| 25 |
continue
|
| 26 |
if not href.startswith("http"):
|
|
|
|
| 28 |
if href in seen:
|
| 29 |
continue
|
| 30 |
seen.add(href)
|
|
|
|
| 31 |
title = a.get("title", "") or ""
|
| 32 |
img = a.find("img")
|
| 33 |
if img and not title:
|
|
|
|
| 36 |
title = a.get_text(strip=True)
|
| 37 |
if not title or len(title) < 10:
|
| 38 |
continue
|
|
|
|
| 39 |
img_src = None
|
| 40 |
if img:
|
| 41 |
img_src = img.get("data-original") or img.get("data-src") or img.get("src") or ""
|
| 42 |
if "base64" in img_src or len(img_src) < 20:
|
| 43 |
img_src = None
|
|
|
|
| 44 |
if not img_src:
|
| 45 |
parent = a.parent
|
| 46 |
if parent:
|
|
|
|
| 50 |
if "base64" in img_src or len(img_src) < 20:
|
| 51 |
img_src = None
|
| 52 |
if not img_src:
|
| 53 |
+
continue
|
| 54 |
articles.append({
|
| 55 |
"title": title, "link": href, "img": img_src,
|
| 56 |
"source": "24h", "is_video": True
|
|
|
|
| 60 |
return []
|
| 61 |
|
| 62 |
|
| 63 |
+
def _extract_24h_video_url(article_url):
|
| 64 |
+
"""Extract m3u8 video URL + poster from a 24h article page.
|
| 65 |
+
Returns dict {src, poster, vtype} or None."""
|
| 66 |
+
try:
|
| 67 |
+
r = requests.get(article_url, headers=HEADERS, timeout=10)
|
| 68 |
+
r.encoding = "utf-8"
|
| 69 |
+
m3u8s = re.findall(r'(https?://cdn\.24h\.com\.vn/[^\s"\'\\]+\.m3u8)', r.text)
|
| 70 |
+
full = [u for u in m3u8s if '_720p' not in u]
|
| 71 |
+
if not full:
|
| 72 |
+
full = m3u8s
|
| 73 |
+
if not full:
|
| 74 |
+
return None
|
| 75 |
+
soup = BeautifulSoup(r.text, "lxml")
|
| 76 |
+
og = soup.find("meta", property="og:image")
|
| 77 |
+
poster = og.get("content", "") if og else ""
|
| 78 |
+
return {"src": full[0], "poster": poster, "vtype": "hls"}
|
| 79 |
+
except:
|
| 80 |
+
return None
|
| 81 |
+
|
| 82 |
+
|
| 83 |
# CSS for the 9:16 shorts slider
|
| 84 |
SHORTS_CSS = """
|
| 85 |
/* Shorts 9:16 Slider */
|
|
|
|
| 94 |
|
| 95 |
def render_shorts_carousel(shorts, esc_fn, safe_url_fn, make_id_fn, slug_fn):
|
| 96 |
"""Render 9:16 vertical shorts carousel HTML.
|
| 97 |
+
Click opens the shorts article in TikTok fullscreen mode."""
|
|
|
|
| 98 |
vids = [v for v in shorts if v.get("img")]
|
| 99 |
if not vids:
|
| 100 |
return ""
|
|
|
|
| 105 |
title = v.get("title", "")
|
| 106 |
aid = make_id_fn(link)
|
| 107 |
sl = slug_fn(title)
|
| 108 |
+
# bdpOpen opens the article - which now renders as TikTok feed for 24h
|
| 109 |
cjs = "window.bdpOpen('" + esc_fn(link) + "','" + aid + "','" + sl + "')"
|
| 110 |
items.append(
|
| 111 |
'<div class="vslide-shorts-item" onclick="' + cjs + '">'
|