Scraper de canales de YouTube hacia notas Markdown para base de conocimiento (Obsidian-ready), con plataforma web local. Engine + CLI (Workstream A): - Modular pipeline: discover/extract/parse/chapters/render/store + ratelimit - SQLite store con migración idempotente: FTS5 (transcript search), columnas de metadata enriquecida, tablas cookies_meta y scrape_jobs - Módulos: segments, cookies (Netscape vault), export (json/csv/srt/html), analysis (word freq/timeline/wordcloud), monitor (watch loop), pipeline - CLI Click group: search, export, audio, channels, watch, analyze, re-render - Fix del bug de scoping de cookies en cli.py Webapp local (Workstream B): - FastAPI backend: dashboard, channels, videos facetado, transcript, search FTS, analysis, scrape jobs con SSE, cookies drag-and-drop, exports, folders (abrir en OS), tools (re-render, formato) - SPA no-build (Alpine.js + Tailwind + Chart.js por CDN): 9 vistas, tema dark command-center con acento rojo→rosa, cookie vault drag-drop, consola de scrapeo con progreso live vía SSE Launcher + subagentes (Workstream C): - start-server.bat / stop-server.bat con auto port-scan + browser open - .opencode/agent/webapp-builder.md + .opencode/goals/webapp-build.md Tests: 38 pytest verdes. Sin funcionalidad de IA (enfoque data-mining).
7.1 KiB
yt-scraper Platform Implementation Plan
Execution mode: Subagent-driven + inline, autonomous, self-iterating. User mandated "completa todo el proceso de manera agentica y subagentica creando y resolviendo todos los problemas y dudas en un loop auto iterativo." Proceed without further checkpoints; verify with tests and fix in a loop until green.
Goal: Turn the CLI scraper into a local content-mining platform: CLI features (no AI) + FastAPI/Alpine webapp + .bat launcher + opencode subagent.
Architecture: Monorepo. Store extends with non-destructive migration (segments FTS5 + metadata + cookies + jobs). New modules segments/cookies/export/analysis/monitor. FastAPI app at src/yt_scraper/webapp/ serves a no-build Alpine SPA. .bat launcher auto-scans ports.
Tech: Python ≥3.10, FastAPI, uvicorn, Alpine.js + Tailwind + Chart.js (CDN), SQLite FTS5, yt-dlp, matplotlib, Jinja2.
Spec: docs/superpowers/specs/2026-07-26-platform-design.md
File map & responsibilities
| File | Responsibility | Status |
|---|---|---|
src/yt_scraper/store.py |
Add migration (new cols + 3 new tables), segment/cookie/job queries | modify |
src/yt_scraper/segments.py |
store_segments, populate_fts, search, backfill_from_markdown |
new |
src/yt_scraper/cookies.py |
Netscape parse/validate, vault CRUD, resolve_active_path |
new |
src/yt_scraper/export.py |
json/csv/srt/html exporters | new |
src/yt_scraper/analysis.py |
word freq / top-N / timeline (data + optional matplotlib) | new |
src/yt_scraper/monitor.py |
watch_loop(config, interval, once) |
new |
src/yt_scraper/cli.py |
Click group + subcommands; fix cookies bug | modify |
src/yt_scraper/extract.py |
unchanged (already accepts cookies) | - |
src/yt_scraper/webapp/app.py |
FastAPI factory + static + healthz | new |
src/yt_scraper/webapp/api.py |
REST routers | new |
src/yt_scraper/webapp/jobs.py |
scrape job runner + SSE bus | new |
src/yt_scraper/webapp/static/{index.html,app.js,styles.css} |
SPA | new |
templates/gallery.html.j2 |
HTML export gallery | new |
start-server.bat, stop-server.bat |
launcher | new |
.opencode/agent/webapp-builder.md, .opencode/goals/webapp-build.md |
subagent | new |
tests/ |
cover migration, segments, cookies, export, analysis, cli | new/extend |
Key interfaces (contract between modules)
# segments.py
@dataclass
class Hit: video_id: str; channel_id: str; title: str; start_sec: float; end_sec: float; snippet: str; rank: float
def store_segments(store, video_id, segments: list[Segment]) -> None
def search(store, query: str, channel_id: str|None=None, limit: int=50) -> list[Hit]
def backfill_from_markdown(store, md_root: Path, log=print) -> int # returns n videos
# cookies.py
@dataclass
class CookieMeta: id:str; filename:str; label:str|None; added_at:str; expires_at:str|None; is_active:bool; has_session:bool; cookie_count:int
def parse_netscape(path) -> tuple[bool, dict] # (ok, {expires_at, has_session, count})
def import_file(store, path) -> str # id
def import_text(store, text, label) -> str
def set_active(store, cookie_id) -> None
def list_vault(store) -> list[CookieMeta]
def delete(store, cookie_id) -> None
def resolve_active_path(store) -> str|None
def auto_import_dir(store, dir_path) -> None # import loose .txt in cookies/
# export.py
def export_json(store, segments_store_fn, channel_id, out_path) -> Path
def export_csv(store, channel_id, out_path) -> Path
def export_srt(store, video_id, out_path) -> Path
def export_html(store, channel_id, out_path, template_path) -> Path
# analysis.py
def word_frequency(store, channel_id=None) -> dict[str,int]
def top_words(store, n=50, channel_id=None) -> list[tuple[str,int]]
def term_timeline(store, term, channel_id=None) -> list[tuple[str,int]] # (YYYY-MM, count)
def render_wordcloud(freq, out_path) -> None # matplotlib
# monitor.py
def watch_loop(cfg, interval_seconds, channel_id=None, once=False) -> None
Tricky bits (resolved decisions)
- FTS5 sync: manual
populate_fts(delete+insert) afterstore_segments. No triggers (simpler, idempotent). FTS uses external-contenttranscript_ftswithcontent='transcript_segments'; queries viaMATCH+ join. - Migration:
ALTER TABLE ADD COLUMNis guarded byPRAGMA table_infochecks (idempotent). New tables viaCREATE TABLE IF NOT EXISTS. - Backfill parse: re-use existing
.mdformat: lines**MM:SS** · textunder### Title (MM:SS)chapter headers; frontmatter between---fences has YAML metadata. Reuseparse.Segment+chapters.Section. - Cookie active: single row
is_active=1;set_activeflips all others to 0 in one tx. - Click structure: convert top-level
@click.commandto@click.groupwith the original scrape flow underscrapecommand (default invocation preserved via a wrapper), plussearch/export/audio/channels/watch/analyze/re-render. - Port scan: PowerShell
System.Net.Sockets.TcpListenerbind-test 8000..8100. - SSE:
EventSourceResponse-style viasse-starletteOR a hand-rolled streamingResponsewithtext/event-stream. Usesse-starlette(add to deps) — cleaner.
Task list (build order)
A1 — Foundation
- T1: Store migration (new cols +
transcript_segments+transcript_fts+cookies_meta+scrape_jobs) + queries. Test: migration idempotent, columns exist, tables exist. - T2:
segments.py(store_segments,populate_fts,search). Test: store + search returns Hit. - T3:
cookies.py. Test: parse real cookie file, validate, active toggling, resolve path. - T4: Fix cli.py cookies bug (thread cookies through
_process_one; fallback to active vault cookie). - T5:
backfill_from_markdown. Test: parse one real.md, assert segments inserted.
A2 — Feature modules
- T6:
export.py(json/csv/srt/html) +gallery.html.j2. Test: each format round-trips. - T7:
analysis.py(freq, top, timeline, wordcloud). Test: counts on synthetic segments. - T8:
monitor.py(watch_loop,--once). Test: single iteration with mocked discover.
A3 — CLI wiring
- T9: Convert
cli.pyto Click group; addsearch,export,audio,channels,watch,analyze,re-render,--all-channels. Smoke-test each viaCliRunner.
A4 — Tests green
- T10: Full
pytestgreen; integration tests marked.
B — Webapp (subagent: webapp-builder)
- T11:
.opencode/agent/webapp-builder.md+.opencode/goals/webapp-build.md. - T12: webapp backend (
app.py,api.py,jobs.py) — all routers + SSE. - T13: SPA (
index.html,app.js,styles.css) — 9 views, dark command-center, drag-drop cookies, Chart.js, SSE scrape console. - T14: Verify
/healthz+ smoke each endpoint; auto-import real cookie on startup.
C — Launcher + deps
- T15:
pyproject.tomloptional deps (web,analysis);.gitignore(cookies/,.run/,data/analysis/). - T16:
start-server.bat+stop-server.bat(PowerShell port scan). - T17: End-to-end:
start-server.bat→ browser →/healthz200 → dashboard loads.
Verification loop
- Run
pytest, fix until green. Runstart-server.bat, confirm health + SPA. Iterate.