Cómo crear un scraper de noticias y enviarlas a Telegram con Python

Publicado el: 11/07/2026
Tempo de leitura: 5 minutos
Web scraper de notícias em Python com envio para Telegram

Un scraper de noticias con envío a Telegram combina tres tareas: descargar una página, extraer titulares y publicar un mensaje mediante un bot. Es un proyecto práctico para aprender solicitudes HTTP, análisis de HTML, deduplicación, variables de entorno, registros y automatización. La parte más importante no es únicamente obtener texto, sino hacerlo de forma responsable, resistente a cambios y sin enviar la misma noticia repetidamente.

Antes de comenzar conviene revisar la guía de web scraping con Beautiful Soup y Requests, el tutorial de Requests en Python, la creación de un bot de Telegram, el uso de variables de entorno y la guía de logging.

Las referencias externas principales son la documentación oficial de Requests, la documentación de Beautiful Soup y la documentación oficial de Telegram Bot API.

Revisar permisos antes de extraer datos

No todas las páginas permiten scraping. Consulta los términos del sitio, su archivo robots.txt, las licencias del contenido y cualquier API oficial disponible. No intentes eludir autenticación, bloqueos, captchas o límites. Este tutorial debe aplicarse únicamente a fuentes que permitan el acceso automatizado y a datos públicos.

Crear el entorno

mkdir noticias_telegram
cd noticias_telegram
python -m venv .venv

Activa el entorno e instala las dependencias:

python -m pip install requests beautifulsoup4 python-dotenv

Guarda las versiones en un archivo:

python -m pip freeze > requirements.txt

Crear el bot de Telegram

Utiliza BotFather dentro de Telegram para crear un bot y recibir un token. No publiques ese token ni lo pegues en el código. Para conocer el identificador del chat, envía un mensaje al bot y consulta las actualizaciones durante la configuración, respetando la documentación oficial.

Guardar secretos en variables de entorno

Crea un archivo .env local:

TELEGRAM_BOT_TOKEN=tu_token
TELEGRAM_CHAT_ID=tu_chat_id

Añádelo a .gitignore:

.env
.venv/
__pycache__/
noticias_enviadas.json

Carga la configuración:

import os
from dotenv import load_dotenv

load_dotenv()

TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")

if not TOKEN or not CHAT_ID:
    raise RuntimeError("Faltan las variables de Telegram")

Descargar una página con Requests

import requests

URL_FUENTE = "https://ejemplo.com/noticias"


def descargar_html(url):
    encabezados = {
        "User-Agent": "NoticiasEducativasBot/1.0 [email protected]"
    }
    respuesta = requests.get(
        url,
        headers=encabezados,
        timeout=(5, 20),
    )
    respuesta.raise_for_status()
    return respuesta.text

Utiliza un User-Agent identificable cuando la fuente lo permita, añade un medio de contacto y define timeout. raise_for_status() transforma respuestas HTTP de error en excepciones.

Crear una sesión reutilizable

sesion = requests.Session()
sesion.headers.update({
    "User-Agent": "NoticiasEducativasBot/1.0 [email protected]"
})


def descargar_html(url):
    respuesta = sesion.get(url, timeout=(5, 20))
    respuesta.raise_for_status()
    return respuesta.text

Una sesión reutiliza conexiones y permite configurar encabezados comunes. No aumenta automáticamente el ritmo permitido; conserva pausas razonables y respeta límites.

Inspeccionar el HTML

Abre las herramientas del navegador y localiza el elemento que contiene cada noticia. Una estructura hipotética podría ser:

<article class="noticia">
  <h2><a href="/noticias/python-actualizado">Python recibe una actualización</a></h2>
</article>

Los selectores dependen del sitio real. No copies un selector sin revisarlo y evita depender de clases generadas que cambian en cada despliegue.

Extraer titulares y enlaces

from bs4 import BeautifulSoup
from urllib.parse import urljoin


def extraer_noticias(html, url_base):
    soup = BeautifulSoup(html, "html.parser")
    noticias = []

    for enlace in soup.select("article.noticia h2 a"):
        titulo = enlace.get_text(" ", strip=True)
        href = enlace.get("href")

        if not titulo or not href:
            continue

        noticias.append({
            "titulo": titulo,
            "url": urljoin(url_base, href),
        })

    return noticias

urljoin convierte rutas relativas en URLs completas. Limpiar el texto con get_text evita espacios y saltos innecesarios.

Validar los resultados

def validar_noticias(noticias):
    validas = []
    vistas = set()

    for noticia in noticias:
        titulo = noticia["titulo"][:300]
        url = noticia["url"]

        clave = (titulo.casefold(), url)
        if clave in vistas:
            continue

        if not url.startswith(("http://", "https://")):
            continue

        vistas.add(clave)
        validas.append({"titulo": titulo, "url": url})

    return validas

Limitar longitudes protege la salida y evita mensajes enormes. Una validación más estricta puede comprobar que los enlaces pertenezcan al dominio esperado.

Enviar un mensaje a Telegram

def enviar_telegram(texto):
    url = f"https://api.telegram.org/bot{TOKEN}/sendMessage"
    datos = {
        "chat_id": CHAT_ID,
        "text": texto,
        "disable_web_page_preview": True,
    }

    respuesta = requests.post(
        url,
        json=datos,
        timeout=(5, 20),
    )
    respuesta.raise_for_status()
    resultado = respuesta.json()

    if not resultado.get("ok"):
        raise RuntimeError("Telegram rechazó el mensaje")

    return resultado

Enviar JSON evita construir manualmente la query. No registres el URL completo porque contiene el token.

Formatear las noticias

def formatear_noticia(noticia):
    return f"{noticia['titulo']}\n{noticia['url']}"

Telegram admite formatos adicionales, pero el texto plano reduce errores de escape. Si utilizas HTML o Markdown, escapa correctamente el contenido extraído.

Evitar envíos duplicados

Guarda identificadores de noticias ya publicadas:

import hashlib
import json
from pathlib import Path

RUTA_ESTADO = Path("noticias_enviadas.json")


def crear_id(noticia):
    contenido = noticia["url"].encode("utf-8")
    return hashlib.sha256(contenido).hexdigest()


def cargar_enviadas():
    if not RUTA_ESTADO.exists():
        return set()
    try:
        datos = json.loads(RUTA_ESTADO.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return set()
    return set(datos)


def guardar_enviadas(ids):
    temporal = RUTA_ESTADO.with_suffix(".tmp")
    temporal.write_text(
        json.dumps(sorted(ids), indent=2),
        encoding="utf-8",
    )
    temporal.replace(RUTA_ESTADO)

La escritura temporal disminuye el riesgo de dejar un archivo incompleto. Para varios procesos simultáneos, utiliza una base de datos o un mecanismo de bloqueo.

Unir el flujo completo

def ejecutar():
    enviadas = cargar_enviadas()
    html = descargar_html(URL_FUENTE)
    noticias = validar_noticias(
        extraer_noticias(html, URL_FUENTE)
    )

    nuevas = [
        noticia for noticia in noticias
        if crear_id(noticia) not in enviadas
    ]

    for noticia in nuevas[:5]:
        enviar_telegram(formatear_noticia(noticia))
        enviadas.add(crear_id(noticia))

    guardar_enviadas(enviadas)


if __name__ == "__main__":
    ejecutar()

El límite de cinco evita inundar el chat si el estado se pierde o el selector cambia. Ajusta la cantidad a una frecuencia razonable.

Añadir logging

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)

logging.info("Iniciando consulta de noticias")

Registra cantidad de noticias encontradas, nuevas y enviadas, pero nunca tokens ni contenido privado.

Manejar errores de red

def main():
    try:
        ejecutar()
    except requests.Timeout:
        logging.error("La solicitud superó el tiempo límite")
    except requests.HTTPError as error:
        logging.error("Error HTTP: %s", error.response.status_code)
    except requests.RequestException as error:
        logging.error("Error de red: %s", error)
    except Exception:
        logging.exception("Fallo inesperado")

Separar timeouts, errores HTTP y fallos inesperados facilita diagnosticar el problema. No reintentes indefinidamente.

Implementar reintentos limitados

import time


def ejecutar_con_reintentos(max_intentos=3):
    for intento in range(1, max_intentos + 1):
        try:
            ejecutar()
            return
        except requests.RequestException:
            if intento == max_intentos:
                raise
            espera = 2 ** intento
            time.sleep(espera)

El backoff exponencial reduce presión durante una falla. Si el servidor devuelve Retry-After, respeta esa indicación.

Programar la ejecución

En Windows puedes usar el Programador de tareas; en Linux, cron o un servicio. Ejecuta el script con el Python del entorno virtual y configura el directorio de trabajo. Una frecuencia de quince minutos o una hora suele ser más razonable que consultar cada pocos segundos, pero depende del permiso y de la fuente.

Detectar cambios en el HTML

Un selector roto puede devolver cero resultados sin generar excepción. Añade una comprobación:

if not noticias:
    raise RuntimeError("No se encontraron noticias; revisa el selector")

Guarda una pequeña muestra HTML para pruebas, sin redistribuir contenido protegido. Las pruebas deben confirmar que el parser extrae títulos, resuelve enlaces y elimina duplicados.

Preferir RSS o API cuando existan

Si la fuente ofrece RSS o una API, normalmente son opciones más estables y explícitas que analizar HTML. RSS conserva títulos, enlaces y fechas en una estructura pensada para sindicación. El scraper debería ser la alternativa cuando el acceso esté permitido y no exista un canal oficial apropiado.

Seguridad de URLs y contenido

No sigas enlaces extraídos para descargar archivos automáticamente sin validar dominio, esquema, tamaño y tipo. Si el scraper recibe una URL configurable, protege el sistema contra solicitudes a redes internas. Mantén el bot en un chat controlado y limita comandos y permisos.

Errores frecuentes

Los fallos habituales son guardar el token en el código, consultar demasiado rápido, ignorar términos del sitio, usar selectores frágiles, enviar noticias duplicadas y no definir timeout. También es peligroso registrar el endpoint completo de Telegram, porque contiene la credencial del bot.

Conclusión

Un scraper de noticias confiable descarga páginas con límites, analiza HTML mediante selectores revisados, valida enlaces, conserva un estado de deduplicación y envía mensajes sin exponer el token. Respeta las reglas de la fuente y prefiere APIs o RSS cuando estén disponibles. Con logging, pruebas y reintentos limitados, el proyecto puede ejecutarse de forma automatizada sin convertirse en una carga para el sitio ni para el chat.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Logos do Python e Excel lado a lado representando a importação de dados do Excel para Python.
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo importar datos de Excel en Python con Pandas

    Importa Excel con Pandas y openpyxl: selecciona hojas y columnas, convierte fechas y números, valida el esquema y exporta datos

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Automação em Python para organizar arquivos do computador
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo organizar archivos del PC automáticamente con Python

    Organiza archivos del PC con Python usando pathlib y shutil, evita sobrescrituras, registra movimientos y añade modo simulación.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Compactação de arquivos ZIP usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo descomprimir archivos ZIP con Python de forma segura

    Descomprime archivos ZIP con Python de forma segura: valida rutas, evita Zip Slip, limita tamaños, verifica integridad y procesa archivos

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Python e ícone de e-mail sobre teclado de notebook
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo automatizar correos electrónicos con Python

    Automatiza correos con Python usando EmailMessage y smtplib: texto, HTML, adjuntos, TLS, variables de entorno, reintentos y prevención de duplicados.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Acesso e edição de Google Sheets com Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo acceder y editar Google Sheets con Python

    Accede y edita Google Sheets con Python y gspread: autenticación, lectura, escritura por lotes, validación, permisos, reintentos y seguridad.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Automação de mensagens no WhatsApp usando Python
    Automatización y Scripts
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear un bot de WhatsApp con Python de forma responsable

    Crea un bot de WhatsApp con Python usando PyWhatKit o la Cloud API oficial, con validación, seguridad, colas, pruebas y

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026