Fuentes de sentimiento sin comprometer cuenta — Surtidito

2026-09-06 · verificado en vivo (curl) contra las APIs nombradas

Contexto

feed.py ya trae X vía RSSHub local (token de sesión propio, grey zone ToS) y RSS de medios. Para análisis de sentimiento se necesitan corpus con VOLUMEN y RESPUESTAS, sin exponer la cuenta personal.

Auditoría de la respuesta de otro asistente (lo que está mal)

  • snscrape → MUERTO para X. Última release 2023-06; X cerró la guest API en julio-2023. No devuelve nada. No usar.
  • pysentimiento/spanish-twitter-sentiment → dataset INVENTADO. Lo real es el MODELO pysentimiento/robertuito-sentiment-analysis (verificado HF API).
  • cardiffnlp/twitter-xlm-roberta-base-sentiment → es modelo, no dataset (verificado). Sirve como clasificador EN/multilingüe.
  • kazanova/sentiment140 → real pero tweets de 2009 en inglés. Inútil para sentimiento MX-2026.
  • Mastodon scraping con BeautifulSoup → innecesario, pero ojo: el timeline público de mastodon.social AHORA exige auth (verificado: 401 anónimo). Token read-only gratis, 2 min.
  • datos sintéticos para MEDIR sentimiento → circular: no puedes generar el dato que mides. Solo valen para fine-tunear el clasificador.

Lo que sí funciona (verificado hoy)

  1. Bluesky API pública SIN cuenta — https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=... responde 200 anónimo. El SDK atproto ni siquiera hace falta para leer. ⚠️ Cobertura MX fina: los medios de FUENTES no tienen cuenta oficial (solo periodistas individuales). Busque “contralinea”, “rompeviento”, etc.
  2. Reddit (PRAW) — app gratuita, OAuth 100 qpm, r/mexico tiene volumen real de noticias MX y los COMENTARIOS son respuestas genuinas = el único corpus con reply-level sentiment accesible legalmente hoy.
  3. RSS de los propios medios — ya cubierto en feed.py, cero riesgo.

Stack recomendado (mínimo real)

  • Recolección: función obtener_bluesky(actor) + obtener_reddit(sub, q) en feed.py, mismo formato de item ({titulo, publicado, url, medio, tipo}). Sin cuenta propia = sin nada que bloquear.
  • Clasificador local: pysentimiento/robertuito-sentiment-analysis (entrenado en tweets ES, corre en CPU; encaja local-first).
  • Agregación: columna sentimiento en los items del feed → nlp.py promedia por tema/día (ya tiene la machinery de emergentes).
  • Validación: buscar datasets ES anotados en HF con api.list_datasets(search="sentiment spanish") — solo para calibrar el modelo, no como corpus vivo.

twikit (d60/twikit) — auditado mismo día

  • Features que RSSHub no da: search por consulta, replies, métricas (likes/RTs).
  • PERO: requiere login con credenciales (riesgo de baneo de cuenta; el repo trae ToProtectYourAccount.md). PyPI congelado en 2.3.3 (feb-2025), último commit real abr-2025. Issues 2026: KeyError post-abr-2026, SearchTimeline 404, 429 constantes, y X pidiendo palm-liveness scan al loguear (jul-2026).
  • Veredicto: solo para deep-dive manual con cuenta desechable + cookies reutilizadas. Nunca en cron diario. El pipeline vive en Reddit/Bluesky/RSS.

twitterapi.io — PROBADO EN VIVO 2026-09-06 (key en .env.local)

Funciona y cuesta centavos. Recipes verificados:

  • GET /twitter/user/info?userName=X → OK (perfiles vivos).
  • GET /twitter/user/tweets?userName=X → MUERTO: devuelve “User is suspended” hasta para elonmusk (pool de scrape quemado en ese endpoint). NO USAR.
  • GET /twitter/tweet/advanced_search?query=from:handle&queryType=Latest → OK: 20 tweets frescos con retweetCount/likeCount/viewCount/lang. El reemplazo directo de user/tweets. Soporta min_replies:, since_time:.
  • GET /twitter/tweet/replies/v2?tweetId=X → OK: replies reales con autor y likes (34 de 191 en la prueba). Esto es el corpus de sentiment.
  • Precio test completo (6 llamadas, ~100 tweets): $0.014.
  • Plan de integración: en feed.py una obtener_x(handle) que use advanced_search from: y guarde JSON crudo en data/x/ (el dato pagado se persiste; si el proveedor muere, el corpus queda). Sentimiento: replies/v2 solo para tweets con replyCount>umbral. Docs: https://docs.twitterapi.io/llms.txt

SerpApi y Cloudflare Browser Run — veredicto 2026-09-06

  • SerpApi: su motor Google-Twitter MURIÓ (Google mató la SERP de Twitter; serpapi.com/google-twitter-api → 404). Lo que queda (Google News/Bing, $75/5k búsquedas) es lo mismo que Google News RSS gratis — ya integrado en sentimiento.py como collect_gnews (45 titulares/3 temas, balance -0.53). Contratar SerpApi hoy = pagar por un RSS gratuito. No.
  • Cloudflare Browser Run (ex Browser Rendering, renombre abr-2026): free 10 min browser/día; paid 10h + $0.09/h extra. NO sirve para X: muro de login para navegadores anónimos + AS13335 ya flaggeado; meter tus cookies de X ahí = el mismo riesgo con IP ajena. SÍ sirve como upgrade de /leer: quick action POST /markdown para páginas JS-pesadas (fuentes CN tech) donde trafilatura se queda corto. Token MCP actual sin scope browser-rendering.
  • Corrida 2026-09-06 2a: 197 textos / 5 fuentes (gnews nuevo), misma firma negativa: Culiacán -0.56, judiciales -0.58, Sheinbaum -0.36.

Fuentes añadidas

  • fast.ai (2026-09-06): feed real = https://www.fast.ai/index.xml (RSS2; /feed.xml y /feeds.xml dan 404). Añadida a TECH_RSS en feed.py, categoria AI, en=True → entra al feed-tech y al hn-tech con traduccion automatica. Post frecuente para el boletin tech: “My friends all hate AI…” ago-2026.

Reddit MUERTO para devs pequeños (r/redditdev, verificado sep-2026)

  • Responsible Builder Policy (nov-2025): prefs/apps redirige a la politica, Data Access Requests rechazadas con plantilla, clientes OAuth legados revocados, RSS a ~1 req/60s, .json 403. NO hay ruta self-serve — borrar de cualquier plan la idea de “crear la app”.
  • Nuevas fuentes de RESPUESTAS ES verificadas hoy:
    1. Bluesky getPostThread — ANONIMO funciona (depth=2). Implementado como expandir_hilos_bluesky() en sentimiento.py (tope 8 hilos/corrida).
    2. YouTube via yt-dlp —write-comments — el default client sirve los comentarios (video Ayotzinapa: 14/14; player_client=tv NO). Filtro antispam (paypal/donaciones/urls-solamente). collect_yt + flag —yt. Es EL corpus ES de replies con volumen, sin cuenta, sin API key. Truco: videos muy nuevos = 0 comentarios; usar los de dias atras.
    3. HN Algolia (anonimo, con volumen) — solo util para lo TECH (EN) y robertuito no puntua EN; esperar a tener clasificador EN si acaso.
  • sentimiento.py ahora reporta buckets separados: bluesky / bluesky (respuestas) / youtube / reddit / gnews / newsapi / mastodon / prensa. Los logs de Reddit dicen la verdad del cierre.

Pulso: X solo a peticion expresa 2026-09-06 (tarde)

  • pulso.py default = MODO SOCIAL (reddit+bluesky+newsapi via sentimiento.py; sin ninguna cuenta X). pulso.py --x = tuits feed.json + metricas twitterapi.io (~$0.01). Actualizar_hn.sh usa el default.
  • Bug cazado: generar_social no cargaba dotenv → NewsAPI se omitia en silencio (filas=1). Fix: load_dotenv al inicio. Leccion: los colectores deben loguear SIEMPRE su estado, no solo exititos.
  • Nota: sin REDIT_CLIENT_ID, reddit .rss trae likes=0 → nunca rankea arriba; la app de reddit sigue siendo la mejora de mayor impacto pendiente.

Todo local 2026-09-06 (Gemini prepay agotado → ollama por default)

  • proponer._llm_texto default LLM_BACKEND=ollama (gemma4 @ localhost:11434); REMOVIDO format:json de ese branch — era la CAUSA de las traducciones envueltas en {“translation”:…} (el modelo forzaba objeto JSON).
  • nlp._traducir_terminos ya no usa genai directo: via proponer (40/40 EN traducidos local, nube OK).
  • .env.local: LLM_BACKEND=ollama OLLAMA_MODEL=gemma4; portadas IMAGEN_PROVEEDOR=vertex + GCP_PROJECT (ADC vivo) — no verificado con imagen real, primera portada lo dirá.
  • Vercel NO cambia: allá ya tienen LLM_BACKEND en su env de producción.
  • Verificado: traducir 3 titulares ZH frescos → ES limpio sin chatarra.

Version completa 2026-09-06 (preview-edicion con todo)

  • pulso.py --html reusable + canonica reporte/pulso.json|html; paso 2.5 en scripts/actualizar_hn.sh (falla suave).
  • Preview: reporte/2026-09-06-preview.{json,html,md} = edicion aprobada 09-05
    • seccion TECNOLOGIA·CHINA (2 QbitAI, Zhihu, TechNode, Phoronix; titulares chinos TRADUCIDOS, 0 CJK en los 4 renders) + bloque Pulso.
  • Backend LLM: GEMINI_API_KEY agotada (429 prepay) — usar LLM_BACKEND=ollama (gemma4/qwen3.6). traduciones.py endurecido: prompt JSON, unwrap de claves arbitrarias ({“es”:…}), _chatarra() valida cache de lectura y escritura. El cache viejo puede traer porqueria: se ignora solo al leer.

Sección “Pulso en X” del boletín — implementada 2026-09-06

  • pulso.py: feed.json (posts X vía RSSHub, ventana 2d) → twitterapi.io /twitter/tweets (by_ids, 50/lote, 100% metrics) → robertuito → top 6 con techo de 2 por handle (sin techo: AJ inglés se comía el bloque) → reporte/pulso-YYYY-MM-DD.json.
  • Render email-safe reutilizado en 3 lados: reporte.py (web .html/.md) y mailing.py (_cuerpo_html + _cuerpo_texto). Se activa solo si la edición JSON tiene clave “pulso” — no rompe ediciones viejas.
  • Flujos de producción: python pulso.py --edicion reporte/FECHA.json y luego python reporte.py ... / python mailing.py send .... Coste por edición: ~$0.01. Preview: reporte/preview-pulso.html.
  • Enfoque editorial “sin rostro”: el bloque declara método y muestra en el pie (“las etiquetas miden tono, no verdad”) + barra pos/neu/neg.

NewsAPI.org — integrado 2026-09-06 (key solo en .env.local, gitignored)

  • Endpoint útil: GET newsapi.org/v2/everything?q="tema"&language=es (la zona “developer.newsapi.org” NO existe — NXDOMAIN; api.newsapi.org con query param apiKey funciona). top-headlines?country=mx = 0 resultados.
  • Free dev: 100 req/día — el collector usa 1 por tema. Sin comillas en q mezcla basura turca; con q="exacto" + language=es: La Jornada y prensa MX fresca.
  • Corrida con 7 fuentes: 305 textos. Culiacán -0.65 · Sheinbaum -0.40 · judiciales -0.59. newsapi coincide con gnews (cobertura más negativa que nuestros propios titulares, -0.13).

Primera corrida real 2026-09-06 (152 textos, 4 fuentes, $0)

fuentenbalance
mastodon (#hashtags anon)76-0.46
prensa (titulos feed.json)60-0.13
reddit (.rss, titulos)15-0.40
bluesky (feed anon)1—
Temas: Culiacán -0.57 · elecciones judiciales -0.56 · Sheinbaum -0.30.
La prensa propia (neutra) vs la conversación social (negativa) ES el insight.
  • Anonimo disponible YA: mastodon.social + hachyderm.io /timelines/tag/ (el /timelines/public pide token desde 2024-25; el de tags NO).
  • Reddit: hot.rss funciona pero inestable (200→vacio→403); PRAW con app es la version confiable y da COMENTARIOS (el sentiment bueno esta ahi).
  • GDELT: rate-limit agresivo por IP, descartado por ahora.
  • Pendiente humano (~10 min): Reddit app (prefs/apps) → REDDIT_CLIENT_ID/ SECRET; Talkwalker alertas → TALKWALKER_RSS; Bluesky opcional → BSKY_IDENTIFIER/BSKY_APP_PASSWORD (search sin cuenta esta 403 dura).

Implementado 2026-09-06 — sentimiento.py (sin tuits, por decisión)

  • sentimiento.py: Reddit + Bluesky + Talkwalker → robertuito. Corpus persistente en data/corpus/*.jsonl (merge por id al puntuar), resumen en reporte/sentimiento.json (balance por fuente/tema).
  • robertuito validado en CPU (arm64): NEG “mendigo traidor” 0.94, NEU nota climática 0.54, POS elogio 0.94.
  • Bluesky: searchPosts ANONIMA fue CERRADA (403 con cualquier UA); getAuthorFeed sigue 200 anonimo. Con cuenta gratis + app password → BSKY_IDENTIFIER/BSKY_APP_PASSWORD habilita búsqueda por tema.
  • Reddit .json sin OAuth: 403 Blocked. Falta que Inventiva cree la app (reddit.com/prefs/apps, “script”, 2 min) → REDDIT_CLIENT_ID/SECRET.
  • Talkwalker: falta que Inventiva cree alertas (alerts.talkwalker.com, usa el correo del boletin) y pegue los RSS en TALKWALKER_RSS (.env.local, separados por coma). Queries sugeridas: “Sheinbaum”, “Culiacán seguridad”, “periodistas México agresión”.
  • Cron sugerido: python sentimiento.py --limite 25 1x/dia. Cuesta $0 y no toca ninguna identidad X.

Regla de oro

Si un método necesita la cuenta X de Inventiva corriendo un loop, no es para el pipeline diario. RSSHub actual queda solo para display (volumen bajo, fallback a RSS del medio ya existe).