bep40 commited on
Commit
aa0dcfd
·
verified ·
1 Parent(s): 1248ff6

fix: swap xemtivitop primary, remove Dantri/SKDS shorts from yt_scraper.py

Browse files
Files changed (1) hide show
  1. yt_scraper.py +13 -63
yt_scraper.py CHANGED
@@ -1,6 +1,7 @@
1
  """
2
  YouTube Shorts Scraper using yt-dlp (already installed on Space)
3
  Runs yt-dlp as subprocess to extract video info and direct URLs
 
4
  """
5
  import subprocess
6
  import json
@@ -24,7 +25,6 @@ def _set_cache(key, data):
24
  _cache[key] = {'t': time.time(), 'd': data}
25
 
26
  def run_yt_dlp(args, timeout=120):
27
- """Run yt-dlp and return parsed JSON lines"""
28
  try:
29
  result = subprocess.run(
30
  ["yt-dlp"] + args,
@@ -55,155 +55,109 @@ def run_yt_dlp(args, timeout=120):
55
  return []
56
 
57
  def get_channel_shorts_via_playlist(channel_username, max_count=100):
58
- """Get shorts from channel's shorts page using yt-dlp"""
59
- shorts = []
60
-
61
- # Method 1: Fetch from /shorts page
62
  url = f"https://www.youtube.com/@{channel_username}/shorts"
63
  items = run_yt_dlp([
64
- "--dump-json",
65
- "--flat-playlist",
66
- "--no-download",
67
  "--playlist-end", str(max_count),
68
  "--no-check-certificates",
69
  "--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
70
  url
71
  ], timeout=90)
72
-
73
  seen_ids = set()
 
74
  for item in items:
75
  vid = item.get('id', '')
76
  if not vid or vid in seen_ids:
77
  continue
78
  seen_ids.add(vid)
79
-
80
  title = item.get('title', 'VTV Nam Bộ Short')
81
  duration = item.get('duration', 0) or 0
82
-
83
- # Only include actual shorts (<= 120s to be safe)
84
  if duration <= 120 or '#shorts' in title.lower() or '#short' in title.lower():
85
  shorts.append({
86
- 'id': vid,
87
- 'title': title,
88
- 'duration': duration,
89
  'channel': channel_username,
90
  'img': f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg",
91
  })
92
-
93
  return shorts
94
 
95
  def get_channel_videos_filter_shorts(channel_username, max_count=200):
96
- """Get all videos from /videos page and filter for shorts by duration"""
97
- shorts = []
98
-
99
  url = f"https://www.youtube.com/@{channel_username}/videos"
100
  items = run_yt_dlp([
101
- "--dump-json",
102
- "--flat-playlist",
103
- "--no-download",
104
  "--playlist-end", str(max_count),
105
  "--match-filter", "duration > 0 and duration <= 120",
106
  "--no-check-certificates",
107
  "--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
108
  url
109
  ], timeout=120)
110
-
111
  seen_ids = set()
 
112
  for item in items:
113
  vid = item.get('id', '')
114
  if not vid or vid in seen_ids:
115
  continue
116
  seen_ids.add(vid)
117
-
118
  title = item.get('title', 'VTV Nam Bộ Short')
119
- duration = item.get('duration', 0) or 0
120
-
121
  shorts.append({
122
- 'id': vid,
123
- 'title': title,
124
- 'duration': duration,
125
  'channel': channel_username,
126
  'img': f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg",
127
  })
128
-
129
  return shorts
130
 
131
  def get_shorts_with_direct_url(video_ids):
132
- """Get direct video download URLs for given video IDs"""
133
  results = []
134
-
135
- for vid in video_ids[:20]: # Limit to avoid timeout
136
  try:
137
  url = f"https://www.youtube.com/shorts/{vid}"
138
  items = run_yt_dlp([
139
- "--dump-json",
140
- "--no-download",
141
- "--no-check-certificates",
142
- "--format", "best[filesize<10M]/best",
143
- url
144
  ], timeout=30)
145
-
146
  if items:
147
  info = items[0]
148
  direct_url = info.get('url', '')
149
  if not direct_url:
150
- # Try to get from formats
151
  formats = info.get('formats', [])
152
  for f in formats:
153
  if f.get('vcodec') != 'none' and f.get('acodec') != 'none':
154
  direct_url = f.get('url', '')
155
  break
156
-
157
  if direct_url:
158
  results.append({
159
- 'id': vid,
160
- 'title': info.get('title', ''),
161
  'direct_url': direct_url,
162
  'thumbnail': info.get('thumbnail', f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg"),
163
  'duration': info.get('duration', 0),
164
  })
165
  except Exception as e:
166
  print(f"Error getting URL for {vid}: {e}")
167
-
168
  return results
169
 
170
  def get_vtvnambo_shorts(max_count=50):
171
- """Get all shorts from VTV Nam Bộ using yt-dlp"""
172
  cached = _cached('vtvnambo_shorts_yt')
173
  if cached is not None:
174
  return cached
175
-
176
  all_shorts = []
177
  seen_ids = set()
178
-
179
- # Method 1: /shorts page
180
- print(f"[yt-dlp] Fetching /shorts page...")
181
  shorts_page = get_channel_shorts_via_playlist('vtvnambo', max_count)
182
  for s in shorts_page:
183
  if s['id'] not in seen_ids:
184
  seen_ids.add(s['id'])
185
  all_shorts.append(s)
186
- print(f"[yt-dlp] /shorts page: {len(shorts_page)} shorts")
187
-
188
- # Method 2: /videos page with duration filter
189
  if len(all_shorts) < 5:
190
- print(f"[yt-dlp] Fetching /videos page with filter...")
191
  videos_filtered = get_channel_videos_filter_shorts('vtvnambo', max_count * 2)
192
  for s in videos_filtered:
193
  if s['id'] not in seen_ids:
194
  seen_ids.add(s['id'])
195
  all_shorts.append(s)
196
- print(f"[yt-dlp] /videos filter: {len(videos_filtered)} shorts")
197
-
198
  result = all_shorts[:max_count]
199
  _set_cache('vtvnambo_shorts_yt', result)
200
- print(f"[yt-dlp] Total: {len(result)} shorts from VTV Nam Bộ")
201
  return result
202
 
203
  def get_wc_related_shorts(max_count=30):
204
- """Get World Cup / football related shorts"""
205
  all_shorts = get_vtvnambo_shorts(max_count * 3)
206
-
207
  wc_kws = [
208
  'world cup', 'wc 2026', 'worldcup', 'fifa', 'bóng đá',
209
  'trận đấu', 'đội tuyển', 'tuyển', 'vòng loại',
@@ -219,17 +173,13 @@ def get_wc_related_shorts(max_count=30):
219
  'asian cup', 'aff cup', 'sea games',
220
  'olympic', 'u23', 'u20', 'u17',
221
  ]
222
-
223
  wc_shorts = []
224
  for s in all_shorts:
225
  tl = s.get('title', '').lower()
226
  if any(k in tl for k in wc_kws):
227
  wc_shorts.append(s)
228
-
229
  if not wc_shorts:
230
  wc_shorts = all_shorts
231
-
232
  return wc_shorts[:max_count]
233
 
234
- # Aliases
235
- get_vtvnamo_shorts = get_vtvnambo_shorts
 
1
  """
2
  YouTube Shorts Scraper using yt-dlp (already installed on Space)
3
  Runs yt-dlp as subprocess to extract video info and direct URLs
4
+ Only VTV Nam Bộ — removed Dantri/SKDS entirely
5
  """
6
  import subprocess
7
  import json
 
25
  _cache[key] = {'t': time.time(), 'd': data}
26
 
27
  def run_yt_dlp(args, timeout=120):
 
28
  try:
29
  result = subprocess.run(
30
  ["yt-dlp"] + args,
 
55
  return []
56
 
57
  def get_channel_shorts_via_playlist(channel_username, max_count=100):
 
 
 
 
58
  url = f"https://www.youtube.com/@{channel_username}/shorts"
59
  items = run_yt_dlp([
60
+ "--dump-json", "--flat-playlist", "--no-download",
 
 
61
  "--playlist-end", str(max_count),
62
  "--no-check-certificates",
63
  "--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
64
  url
65
  ], timeout=90)
 
66
  seen_ids = set()
67
+ shorts = []
68
  for item in items:
69
  vid = item.get('id', '')
70
  if not vid or vid in seen_ids:
71
  continue
72
  seen_ids.add(vid)
 
73
  title = item.get('title', 'VTV Nam Bộ Short')
74
  duration = item.get('duration', 0) or 0
 
 
75
  if duration <= 120 or '#shorts' in title.lower() or '#short' in title.lower():
76
  shorts.append({
77
+ 'id': vid, 'title': title, 'duration': duration,
 
 
78
  'channel': channel_username,
79
  'img': f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg",
80
  })
 
81
  return shorts
82
 
83
  def get_channel_videos_filter_shorts(channel_username, max_count=200):
 
 
 
84
  url = f"https://www.youtube.com/@{channel_username}/videos"
85
  items = run_yt_dlp([
86
+ "--dump-json", "--flat-playlist", "--no-download",
 
 
87
  "--playlist-end", str(max_count),
88
  "--match-filter", "duration > 0 and duration <= 120",
89
  "--no-check-certificates",
90
  "--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
91
  url
92
  ], timeout=120)
 
93
  seen_ids = set()
94
+ shorts = []
95
  for item in items:
96
  vid = item.get('id', '')
97
  if not vid or vid in seen_ids:
98
  continue
99
  seen_ids.add(vid)
 
100
  title = item.get('title', 'VTV Nam Bộ Short')
 
 
101
  shorts.append({
102
+ 'id': vid, 'title': title,
103
+ 'duration': item.get('duration', 0) or 0,
 
104
  'channel': channel_username,
105
  'img': f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg",
106
  })
 
107
  return shorts
108
 
109
  def get_shorts_with_direct_url(video_ids):
 
110
  results = []
111
+ for vid in video_ids[:20]:
 
112
  try:
113
  url = f"https://www.youtube.com/shorts/{vid}"
114
  items = run_yt_dlp([
115
+ "--dump-json", "--no-download", "--no-check-certificates",
116
+ "--format", "best[filesize<10M]/best", url
 
 
 
117
  ], timeout=30)
 
118
  if items:
119
  info = items[0]
120
  direct_url = info.get('url', '')
121
  if not direct_url:
 
122
  formats = info.get('formats', [])
123
  for f in formats:
124
  if f.get('vcodec') != 'none' and f.get('acodec') != 'none':
125
  direct_url = f.get('url', '')
126
  break
 
127
  if direct_url:
128
  results.append({
129
+ 'id': vid, 'title': info.get('title', ''),
 
130
  'direct_url': direct_url,
131
  'thumbnail': info.get('thumbnail', f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg"),
132
  'duration': info.get('duration', 0),
133
  })
134
  except Exception as e:
135
  print(f"Error getting URL for {vid}: {e}")
 
136
  return results
137
 
138
  def get_vtvnambo_shorts(max_count=50):
 
139
  cached = _cached('vtvnambo_shorts_yt')
140
  if cached is not None:
141
  return cached
 
142
  all_shorts = []
143
  seen_ids = set()
 
 
 
144
  shorts_page = get_channel_shorts_via_playlist('vtvnambo', max_count)
145
  for s in shorts_page:
146
  if s['id'] not in seen_ids:
147
  seen_ids.add(s['id'])
148
  all_shorts.append(s)
 
 
 
149
  if len(all_shorts) < 5:
 
150
  videos_filtered = get_channel_videos_filter_shorts('vtvnambo', max_count * 2)
151
  for s in videos_filtered:
152
  if s['id'] not in seen_ids:
153
  seen_ids.add(s['id'])
154
  all_shorts.append(s)
 
 
155
  result = all_shorts[:max_count]
156
  _set_cache('vtvnambo_shorts_yt', result)
 
157
  return result
158
 
159
  def get_wc_related_shorts(max_count=30):
 
160
  all_shorts = get_vtvnambo_shorts(max_count * 3)
 
161
  wc_kws = [
162
  'world cup', 'wc 2026', 'worldcup', 'fifa', 'bóng đá',
163
  'trận đấu', 'đội tuyển', 'tuyển', 'vòng loại',
 
173
  'asian cup', 'aff cup', 'sea games',
174
  'olympic', 'u23', 'u20', 'u17',
175
  ]
 
176
  wc_shorts = []
177
  for s in all_shorts:
178
  tl = s.get('title', '').lower()
179
  if any(k in tl for k in wc_kws):
180
  wc_shorts.append(s)
 
181
  if not wc_shorts:
182
  wc_shorts = all_shorts
 
183
  return wc_shorts[:max_count]
184
 
185
+ get_vtvnamo_shorts = get_vtvnambo_shorts