bep40 commited on
Commit
7a44ecb
·
verified ·
1 Parent(s): c2ea453

Upload vtv_shorts.py

Browse files
Files changed (1) hide show
  1. vtv_shorts.py +28 -135
vtv_shorts.py CHANGED
@@ -1,140 +1,33 @@
1
- """
2
- VTV Nam Bộ YouTube Shorts Scraper
3
- """
4
- import requests
5
- import re
6
- import json
7
- import subprocess
8
- import html as html_lib
9
- import time
10
- import threading
11
- from xml.etree import ElementTree as ET
12
 
13
- _cache = {}
14
- _lock = threading.Lock()
15
- CACHE_TTL = 1800
16
 
17
- UA = {
18
- "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",
19
- "Accept-Language": "vi-VN,vi;q=0.9,en;q=0.8",
20
- }
21
-
22
- def _cached(key):
23
- with _lock:
24
- if key in _cache and time.time() - _cache[key]['t'] < CACHE_TTL:
25
- return _cache[key]['d']
26
- return None
27
-
28
- def _set_cache(key, data):
29
- with _lock:
30
- _cache[key] = {'t': time.time(), 'd': data}
31
-
32
- def get_channel_id(username):
33
- cached = _cached(f'ch_id_{username}')
34
- if cached:
35
- return cached
36
- channel_id = None
37
- try:
38
- url = f"https://www.youtube.com/@{username}"
39
- r = requests.get(url, headers=UA, timeout=15)
40
- if r.status_code == 200:
41
- m = re.search(r'<meta\s+property="og:url"\s+content="https://www.youtube.com/channel/(UC[^"]+)"', r.text)
42
- if m:
43
- channel_id = m.group(1)
44
- if not channel_id:
45
- m = re.search(r'"channelId":"(UC[^"]+)"', r.text)
46
- if m:
47
- channel_id = m.group(1)
48
- except Exception as e:
49
- print(f"Error getting channel ID for @{username}: {e}")
50
- if channel_id:
51
- _set_cache(f'ch_id_{username}', channel_id)
52
- return channel_id
53
-
54
- def scrape_via_rss(channel_id, max_count=100):
55
- shorts = []
56
  try:
57
- url = f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}"
58
- r = requests.get(url, headers=UA, timeout=15)
59
- if r.status_code != 200:
60
- return shorts
61
- root = ET.fromstring(r.text)
62
- ns = {'atom': 'http://www.w3.org/2005/Atom', 'yt': 'http://www.youtube.com/xml/schemas/2015'}
63
- for entry in root.findall('atom:entry', ns)[:max_count]:
64
- title_el = entry.find('atom:title', ns)
65
- title = html_lib.unescape(title_el.text) if title_el is not None and title_el.text else ''
66
- vid_el = entry.find('yt:videoId', ns)
67
- vid = vid_el.text if vid_el is not None else ''
68
- if not vid:
69
- continue
70
- is_short = '#shorts' in title.lower() or '#short' in title.lower()
71
- link_el = entry.find('atom:link', ns)
72
- link = link_el.get('href', '') if link_el is not None else ''
73
- if '/shorts/' in link:
74
- is_short = True
75
- if is_short:
76
- shorts.append({'id': vid, 'title': title, 'img': f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg", 'channel': 'vtvnambo'})
77
- except Exception as e:
78
- print(f"RSS scrape error: {e}")
79
- return shorts
80
-
81
- def scrape_via_ydlp(username, count=50):
82
- shorts = []
83
- try:
84
- url = f"https://www.youtube.com/@{username}/shorts"
85
  result = subprocess.run(
86
- ["yt-dlp", "--dump-json", "--flat-playlist", "--no-download", "--playlist-end", str(count), url],
87
- capture_output=True, text=True, timeout=90
 
88
  )
89
- if result.returncode == 0 and result.stdout.strip():
90
- seen_ids = set()
91
- for line in result.stdout.strip().split('\n'):
92
- line = line.strip()
93
- if not line:
94
- continue
95
- try:
96
- entry = json.loads(line)
97
- vid = entry.get('id', '')
98
- if not vid or vid in seen_ids:
99
- continue
100
- seen_ids.add(vid)
101
- title = entry.get('title', 'VTV Nam Bộ Short')
102
- shorts.append({'id': vid, 'title': title, 'img': f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg", 'channel': 'vtvnambo'})
103
- except json.JSONDecodeError:
104
- continue
105
- except (subprocess.TimeoutExpired, FileNotFoundError) as e:
106
- print(f"yt-dlp error: {e}")
107
- return shorts
108
-
109
- def get_vtvnambo_shorts(max_count=50):
110
- cached = _cached('vtvnambo_shorts_v2')
111
- if cached is not None:
112
- return cached
113
- all_shorts = []
114
- seen_ids = set()
115
- channel_id = get_channel_id('vtvnambo')
116
- if channel_id:
117
- rss_shorts = scrape_via_rss(channel_id, max_count * 2)
118
- for s in rss_shorts:
119
- if s['id'] not in seen_ids:
120
- seen_ids.add(s['id'])
121
- all_shorts.append(s)
122
- if len(all_shorts) < 3:
123
- ydlp_shorts = scrape_via_ydlp('vtvnambo', max_count)
124
- for s in ydlp_shorts:
125
- if s['id'] not in seen_ids:
126
- seen_ids.add(s['id'])
127
- all_shorts.append(s)
128
- result = all_shorts[:max_count]
129
- _set_cache('vtvnambo_shorts_v2', result)
130
- return result
131
-
132
- get_vtvnamo_shorts = get_vtvnambo_shorts
133
-
134
- def get_wc_related_shorts(max_count=30):
135
- all_shorts = get_vtvnambo_shorts(max_count * 3)
136
- wc_kws = ['world cup', 'wc 2026', 'worldcup', 'fifa', 'bóng đá', 'trận đấu', 'đội tuyển']
137
- wc_shorts = [s for s in all_shorts if any(k in s.get('title', '').lower() for k in wc_kws)]
138
- if not wc_shorts:
139
- wc_shorts = all_shorts
140
- return wc_shorts[:max_count]
 
1
+ """VTV Shorts - yt-dlp based"""
2
+ import subprocess, json, re, os, time
 
 
 
 
 
 
 
 
 
3
 
4
+ HEADERS = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Accept-Language":"vi-VN,vi;q=0.9,en;q=0.8"}
 
 
5
 
6
+ def _yt_dlp_search(query, count=10):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  result = subprocess.run(
9
+ ["yt-dlp", "--flat-playlist", "--print-json",
10
+ f"ytsearch{count}:{query}", "--quiet"],
11
+ capture_output=True, text=True, timeout=30
12
  )
13
+ items = []
14
+ for line in result.stdout.strip().split('\n'):
15
+ if not line.strip(): continue
16
+ try:
17
+ d = json.loads(line)
18
+ items.append({
19
+ "id": d.get("id",""),
20
+ "title": d.get("title",""),
21
+ "link": d.get("url","") or f"https://www.youtube.com/watch?v={d.get('id','')}",
22
+ "img": f"https://i.ytimg.com/vi/{d.get('id','')}/hqdefault.jpg",
23
+ "source": "yt"
24
+ })
25
+ except: pass
26
+ return items
27
+ except: return []
28
+
29
+ def get_vtvnambo_shorts(count=50):
30
+ return _yt_dlp_search("VTV Nam Bộ tin tức mới nhất", count)
31
+
32
+ def get_wc_related_shorts(count=50):
33
+ return _yt_dlp_search("World Cup 2026 tin tức mới nhất", count)