bep40 commited on
Commit
eae3352
·
verified ·
1 Parent(s): 900d809

revert: shorts_carousel.py back to previous (9:16 ratio, strict regex)

Browse files
Files changed (1) hide show
  1. shorts_carousel.py +26 -73
shorts_carousel.py CHANGED
@@ -12,90 +12,49 @@ BASE_24H = "https://www.24h.com.vn"
12
 
13
 
14
  def scrape_24h_news_shorts():
15
- """Scrape https://www.24h.com.vn/video/video-tin-tuc-cvd769.html
16
- Accept any article link that ends with .html and has content."""
17
  try:
18
  url = f"{BASE_24H}/video/video-tin-tuc-cvd769.html"
19
  r = requests.get(url, headers=HEADERS, timeout=15)
20
  r.encoding = "utf-8"
21
  soup = BeautifulSoup(r.text, "lxml")
22
  articles, seen = [], set()
23
-
24
- # Look at all <article> tags first (more reliable structure)
25
- for art in soup.find_all("article"):
26
- a = art.find("a", href=True)
27
- if not a:
28
- continue
29
  href = a.get("href", "")
30
- if not href or href == "#" or "javascript:" in href:
31
  continue
32
  if not href.startswith("http"):
33
  href = BASE_24H + href
34
- # Skip category/listing pages
35
- if href.endswith("cvd769.html") or "-cvd" in href:
36
- continue
37
  if href in seen:
38
  continue
39
  seen.add(href)
40
-
41
- # Get title from img alt, a title, or text
42
- title = ""
43
- img_tag = art.find("img")
44
- if img_tag:
45
- title = img_tag.get("alt", "")
46
  if not title:
47
- title = a.get("title", "") or a.get_text(strip=True)
48
- if not title or len(title) < 8:
49
  continue
50
-
51
- # Get image
52
  img_src = None
53
- if img_tag:
54
- img_src = img_tag.get("data-original") or img_tag.get("data-src") or img_tag.get("src") or ""
55
- if not img_src or "base64" in img_src or len(img_src) < 20:
56
  img_src = None
57
-
 
 
 
 
 
 
 
58
  if not img_src:
59
  continue
60
-
61
  articles.append({
62
  "title": title, "link": href, "img": img_src,
63
- "source": "24h-shorts", "is_video": True
64
  })
65
-
66
- # Fallback: if <article> approach got nothing, try all <a> with images
67
- if not articles:
68
- for a in soup.find_all("a", href=True):
69
- href = a.get("href", "")
70
- if not href or href == "#" or "javascript:" in href:
71
- continue
72
- if not href.endswith(".html"):
73
- continue
74
- if "-cvd" in href:
75
- continue
76
- if not href.startswith("http"):
77
- href = BASE_24H + href
78
- if href in seen:
79
- continue
80
-
81
- img = a.find("img")
82
- if not img:
83
- continue
84
-
85
- img_src = img.get("data-original") or img.get("data-src") or img.get("src") or ""
86
- if not img_src or "base64" in img_src or len(img_src) < 20:
87
- continue
88
-
89
- title = img.get("alt", "") or a.get("title", "") or a.get_text(strip=True)
90
- if not title or len(title) < 8:
91
- continue
92
-
93
- seen.add(href)
94
- articles.append({
95
- "title": title, "link": href, "img": img_src,
96
- "source": "24h-shorts", "is_video": True
97
- })
98
-
99
  return articles[:20]
100
  except:
101
  return []
@@ -109,7 +68,6 @@ def _extract_24h_video_url(article_url):
109
  r.encoding = "utf-8"
110
  html = r.text
111
 
112
- # Find all m3u8 URLs (normal + escaped)
113
  raw_m3u8 = re.findall(r'(https?://cdn\.24h\.com\.vn/[^\s"\'\\<>]+\.m3u8)', html)
114
  escaped_m3u8 = re.findall(r'(https?:\\/\\/cdn\.24h\.com\.vn\\/[^\s"\'<>]+\.m3u8)', html)
115
  escaped_m3u8 = [u.replace('\\/', '/') for u in escaped_m3u8]
@@ -124,7 +82,6 @@ def _extract_24h_video_url(article_url):
124
  primary_list = full_quality if full_quality else has_720p
125
  base_url = primary_list[0]
126
 
127
- # Get poster
128
  soup = BeautifulSoup(html, "lxml")
129
  og = soup.find("meta", property="og:image")
130
  poster = og.get("content", "") if og else ""
@@ -132,14 +89,12 @@ def _extract_24h_video_url(article_url):
132
  if len(primary_list) > 1:
133
  return {"src": primary_list[0], "poster": poster, "vtype": "hls", "all_parts": primary_list}
134
 
135
- # Probe for additional parts
136
  parts_found = [base_url]
137
 
138
  is_720p = '_720p.m3u8' in base_url
139
  clean_url = base_url.replace('_720p.m3u8', '.m3u8') if is_720p else base_url
140
  name_part = clean_url.replace('.m3u8', '')
141
 
142
- # Pattern A: name ends with digit(s) → increment
143
  m = re.search(r'(\D)(\d{1,2})$', name_part)
144
  if m:
145
  current_num = int(m.group(2))
@@ -161,7 +116,6 @@ def _extract_24h_video_url(article_url):
161
  else:
162
  break
163
 
164
- # Pattern B: CDN article ID increments + part number increments
165
  if len(parts_found) == 1:
166
  m2 = re.match(r'^(.+-)(\d{7,})(-.+?)(\d{1,2})((?:_720p)?\.m3u8)$', base_url)
167
  if m2:
@@ -188,20 +142,19 @@ def _extract_24h_video_url(article_url):
188
  return None
189
 
190
 
191
- # CSS: shorts slide uses 3:4 ratio thumbnail
192
  SHORTS_CSS = """
193
- /* Shorts 3:4 Slider */
194
- .vslide-shorts-item{flex:0 0 130px;scroll-snap-align:start;cursor:pointer;transition:transform .15s}
195
  .vslide-shorts-item:hover{transform:scale(1.04)}
196
- @media(min-width:768px){.vslide-shorts-item{flex:0 0 150px}}
197
- .vslide-shorts-thumb{position:relative;width:100%;aspect-ratio:3/4;border-radius:12px;overflow:hidden;background:#222}
198
  .vslide-shorts-thumb img{width:100%;height:100%;object-fit:cover}
199
  .vslide-shorts-title{color:#ccc;font-size:10.5px;margin:5px 0 0;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
200
  """
201
 
202
 
203
  def render_shorts_carousel(shorts, esc_fn, safe_url_fn, make_id_fn, slug_fn):
204
- """Render 3:4 shorts carousel HTML."""
205
  vids = [v for v in shorts if v.get("img")]
206
  if not vids:
207
  return ""
 
12
 
13
 
14
  def scrape_24h_news_shorts():
15
+ """Scrape https://www.24h.com.vn/video/video-tin-tuc-cvd769.html"""
 
16
  try:
17
  url = f"{BASE_24H}/video/video-tin-tuc-cvd769.html"
18
  r = requests.get(url, headers=HEADERS, timeout=15)
19
  r.encoding = "utf-8"
20
  soup = BeautifulSoup(r.text, "lxml")
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"):
27
  href = BASE_24H + href
 
 
 
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:
34
+ title = img.get("alt", "")
 
 
35
  if 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:
47
+ parent_img = parent.find("img")
48
+ if parent_img:
49
+ img_src = parent_img.get("data-original") or parent_img.get("data-src") or parent_img.get("src") or ""
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
57
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  return articles[:20]
59
  except:
60
  return []
 
68
  r.encoding = "utf-8"
69
  html = r.text
70
 
 
71
  raw_m3u8 = re.findall(r'(https?://cdn\.24h\.com\.vn/[^\s"\'\\<>]+\.m3u8)', html)
72
  escaped_m3u8 = re.findall(r'(https?:\\/\\/cdn\.24h\.com\.vn\\/[^\s"\'<>]+\.m3u8)', html)
73
  escaped_m3u8 = [u.replace('\\/', '/') for u in escaped_m3u8]
 
82
  primary_list = full_quality if full_quality else has_720p
83
  base_url = primary_list[0]
84
 
 
85
  soup = BeautifulSoup(html, "lxml")
86
  og = soup.find("meta", property="og:image")
87
  poster = og.get("content", "") if og else ""
 
89
  if len(primary_list) > 1:
90
  return {"src": primary_list[0], "poster": poster, "vtype": "hls", "all_parts": primary_list}
91
 
 
92
  parts_found = [base_url]
93
 
94
  is_720p = '_720p.m3u8' in base_url
95
  clean_url = base_url.replace('_720p.m3u8', '.m3u8') if is_720p else base_url
96
  name_part = clean_url.replace('.m3u8', '')
97
 
 
98
  m = re.search(r'(\D)(\d{1,2})$', name_part)
99
  if m:
100
  current_num = int(m.group(2))
 
116
  else:
117
  break
118
 
 
119
  if len(parts_found) == 1:
120
  m2 = re.match(r'^(.+-)(\d{7,})(-.+?)(\d{1,2})((?:_720p)?\.m3u8)$', base_url)
121
  if m2:
 
142
  return None
143
 
144
 
 
145
  SHORTS_CSS = """
146
+ /* Shorts 9:16 Slider */
147
+ .vslide-shorts-item{flex:0 0 110px;scroll-snap-align:start;cursor:pointer;transition:transform .15s}
148
  .vslide-shorts-item:hover{transform:scale(1.04)}
149
+ @media(min-width:768px){.vslide-shorts-item{flex:0 0 130px}}
150
+ .vslide-shorts-thumb{position:relative;width:100%;aspect-ratio:9/16;border-radius:12px;overflow:hidden;background:#222}
151
  .vslide-shorts-thumb img{width:100%;height:100%;object-fit:cover}
152
  .vslide-shorts-title{color:#ccc;font-size:10.5px;margin:5px 0 0;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
153
  """
154
 
155
 
156
  def render_shorts_carousel(shorts, esc_fn, safe_url_fn, make_id_fn, slug_fn):
157
+ """Render 9:16 vertical shorts carousel HTML."""
158
  vids = [v for v in shorts if v.get("img")]
159
  if not vids:
160
  return ""