Sección "Lo más visto": vídeos y Shorts populares con contador de vistas

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSyR5Zw5eGj6oPTRVjvP1b
This commit is contained in:
Alex
2026-10-20 16:47:31 +02:00
co-authored by Claude Sonnet 5.5
parent 7b4c8a4d27
commit e8b1f765c8
3 changed files with 93 additions and 5 deletions
+36 -5
View File
@@ -14,6 +14,10 @@ CHANNEL = 'UC8gVtzB3Lqm7nnfMhMvXGxQ'
# YouTube separa cada tipo en su propia lista: UULF = solo vídeos, UUSH = solo Shorts
FEED_VIDEOS = os.environ.get('FEED_VIDEOS_URL', f'https://www.youtube.com/feeds/videos.xml?playlist_id=UULF{CHANNEL[2:]}')
FEED_SHORTS = os.environ.get('FEED_SHORTS_URL', f'https://www.youtube.com/feeds/videos.xml?playlist_id=UUSH{CHANNEL[2:]}')
FEED_POP_VIDEOS = os.environ.get('FEED_POP_VIDEOS_URL', f'https://www.youtube.com/feeds/videos.xml?playlist_id=UULP{CHANNEL[2:]}')
FEED_POP_SHORTS = os.environ.get('FEED_POP_SHORTS_URL', f'https://www.youtube.com/feeds/videos.xml?playlist_id=UUPS{CHANNEL[2:]}')
POP_VIDEOS_SHOWN = 4
POP_SHORTS_SHOWN = 6
VIDEOS_SHOWN = 8 # además del destacado
SHORTS_SHOWN = 12
NS = {'a': 'http://www.w3.org/2005/Atom', 'y': 'http://www.youtube.com/xml/schemas/2015',
@@ -40,16 +44,22 @@ def read_feed(url):
'title': e.find('a:title', NS).text,
'date': f'{int(d[2])} {MESES[int(d[1]) - 1]} {d[0]}',
'desc': e.find('m:group/m:description', NS).text or '',
'views': int((e.find('m:group/m:community/m:statistics', NS).attrib.get('views') or 0)
if e.find('m:group/m:community/m:statistics', NS) is not None else 0),
})
return out
try:
videos, shorts = read_feed(FEED_VIDEOS), read_feed(FEED_SHORTS)
pop_videos, pop_shorts = read_feed(FEED_POP_VIDEOS), read_feed(FEED_POP_SHORTS)
except Exception as exc: # sin red o feed caído: se conserva la web anterior
print('feed no disponible, se mantiene index.html:', exc, file=sys.stderr)
sys.exit(0)
videos, shorts = videos[:VIDEOS_SHOWN + 1], shorts[:SHORTS_SHOWN]
# la lista de populares de YouTube no viene ordenada por vistas: se ordena aquí
pop_videos = sorted(pop_videos, key=lambda v: -v['views'])[:POP_VIDEOS_SHOWN]
pop_shorts = sorted(pop_shorts, key=lambda v: -v['views'])[:POP_SHORTS_SHOWN]
def sync_thumbs(vids):
@@ -73,10 +83,12 @@ def sync_thumbs(vids):
return ok
ok = sync_thumbs(videos + shorts)
ok = sync_thumbs(videos + shorts + pop_videos + pop_shorts)
okids = {v['id'] for v in ok}
videos = [v for v in videos if v['id'] in okids]
shorts = [v for v in shorts if v['id'] in okids]
pop_videos = [v for v in pop_videos if v['id'] in okids]
pop_shorts = [v for v in pop_shorts if v['id'] in okids]
if not videos:
sys.exit(0)
@@ -128,24 +140,30 @@ def url(v):
return f"https://www.youtube.com/watch?v={v['id']}"
def card(v, n):
def vistas(v):
return f"{v['views']:,}".replace(',', '.') + ' vistas' if v['views'] else ''
def card(v, n, show_views=False):
return f'''
<a class="card reveal" style="--d:{(n % 3) * 70}ms" href="{url(v)}" target="_blank" rel="noopener">
<span class="thumb"><img src="img/{v['id']}.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
<span class="meta"><time>{esc(v['date'])}</time><strong>{esc(v['title'])}</strong></span>
<span class="meta"><time>{esc(v['date'])}{' · ' + vistas(v) if show_views and v['views'] else ''}</time><strong>{esc(v['title'])}</strong></span>
</a>'''
def short_card(v, n):
def short_card(v, n, show_views=False):
return f'''
<a class="scard reveal" style="--d:{(n % 6) * 50}ms" href="https://www.youtube.com/shorts/{v['id']}" target="_blank" rel="noopener">
<span class="thumb"><img src="img/{v['id']}.jpg" alt="" loading="lazy" width="480" height="360"><span class="play" aria-hidden="true"></span></span>
<span class="meta"><strong>{esc(v['title'])}</strong></span>
<span class="meta"><strong>{esc(v['title'])}</strong>{'<time>' + vistas(v) + '</time>' if show_views and v['views'] else ''}</span>
</a>'''
cards = ''.join(card(v, i) for i, v in enumerate(rest))
scards = ''.join(short_card(v, i) for i, v in enumerate(shorts))
pcards = ''.join(card(v, i, True) for i, v in enumerate(pop_videos))
pscards = ''.join(short_card(v, i, True) for i, v in enumerate(pop_shorts))
page = f'''<!doctype html>
<html lang="es">
@@ -223,6 +241,19 @@ page = f'''<!doctype html>
</div>
</section>
<section id="populares" class="wrap">
<div class="sec-head reveal">
<h2>Lo más <em>visto</em></h2>
<a class="more" href="{YT}/videos?view=0&amp;sort=p" target="_blank" rel="noopener">Ver populares en YouTube →</a>
</div>
<h3 class="sub reveal">Vídeos</h3>
<div class="grid">{pcards}
</div>
<h3 class="sub reveal">Shorts</h3>
<div class="shorts">{pscards}
</div>
</section>
<section id="programa" class="about wrap reveal">
<h2>De qué va</h2>
<p>Un programa de noche, con tertulia y sin prisa. Cada semana entra gente distinta, con su oficio y su manía, y se habla de lo que sale: de cómo se educa a un adolescente, de la vida en las calles de El Cairo, de si el árabe es tan antiguo como dicen, de qué tienen en común las religiones o de cómo se hace un truco de cartas.</p>