Contexto: cómo obtener imágenes y URLs expandidas (sin t.co) de los likes exportados de X, sin evadir protecciones de X y sin pagar de más.
Herramienta elegida: API pública react-tweet (https://react-tweet.vercel.app/api/tweet/<tweetId>).
- Gratis, sin cookies ni login, sin proxies. Lectura pública, no evade el muro de X.
- Devuelve: mediaDetails[].media_url_https (pbs.twimg.com), expanded_url real,
video.poster + video.variants (mp4/m3u8), entities.urls[].expanded_url,
quoted_tweet y user.screen_name.
- Descarta los tuits borrados/protegidos con 404 (normal).
ScraperAPI NO sirve para X: el tool `scrape` devuelve HTTP 403 en x.com
en TODOS los modos probados (render, premium, ultraPremium). Usar sus proxies
para saltarlo contradice la decisión ia-desde-likes-de-x.md. ScraperAPI sí
puede traer el JSON de react-tweet, pero es redundante: mejor llamar directo.
ScraperAPI MCP expone scrape, ai_parser_*, crawler_* y estructurados de
e-commerce (Amazon/Walmart/eBay/Redfin/Google). No hay endpoint X/Twitter.
Orden de resolución de enlaces (implementado en scripts/likes_enriquecer.py):
1. entities.urls[].expanded_url y mediaDetails/fotos expanded_url, reemplazando
los t.co del texto por orden de aparición según indices[].
2. Si el t.co sobrante es un tuit citado (quoted_tweet), se sustituye por su
permalink https://x.com/<user>/status/<id> (tipo "cita").
3. Fallback final: resolver el t.co por redirect HTTP (301). urllib sigue el
redirect; la URL final se lee con e.geturl() (NO con Location, que viene
vacío cuando el destino da 404). t.co -> 301 -> URL final real.
Formato del archivo de likes:
- like.js es un array de objetos {"like": {tweetId, fullText, expandedUrl}}.
- Parsear con raw[raw.index("["):raw.rindex("]")]; el rsplit(";") rompe por un
";" interno. Luego desanidar x["like"].
- 139,555 likes; solo 3 campos; 0 imágenes; los links son t.co sin resolver.
Resultado con los 50 likes más recientes (2026-09-17):
- 48/50 enriquecidos; 2 fallas = tuits borrados (404).
- 0 links t.co residuales.
- 28 con imagen (31 imágenes, todas pbs.twimg.com), 20 videos mp4.
- 42 con enlaces: media 31, cita 11, externo 5, resuelto 1.
- 45 autores únicos. Salida: data/likes-enriquecidos-50.json
- Script: scripts/likes_enriquecer.py --n 50 (stdlib urllib, sin deps externas).
Capa de memoria local (hecha): scripts/likes_importar.py
- like.js -> data/likes.db (SQLite). Tablas: likes (tweet_id PK, texto,
autor, fecha, permalink, likes, cita, enriquecido, hash), media
(imagen/video), enlaces (tipo, url, dominio), embeddings (vector BLOB
float32), likes_fts (FTS5 sobre texto+autor).
- 139,555 likes importados en ~3s. Fusiona el JSON enriquecido con
--enriquecer (marca enriquecido=1 y llena media/enlaces).
- Embeddings locales con Ollama nomic-embed-text (/api/embeddings).
Incremental y reanudable: --embed inserta solo los que faltan, prioriza
enriquecidos, imprime ritmo y ETA. ~30/s en el S76 => ~78 min para todo.
- Busqueda: scripts/likes_buscar.py "consulta" (semantica, coseno) |
--fts (FTS5/bm25) | --parecido "url o texto" (¿esta fuente se parece a
mis likes?). numpy para el producto punto; normaliza los vectores.
Entorno verificado: sqlite 3.51 con FTS5, numpy y requests en .venv,
Ollama local con qwen3.5:9b, deepseek-r1:8b, nomic-embed-text, qwen3.6,
gemma4. Nada de esto sube datos a terceros.