Telegram News Bot in Python (2026): aiogram + APScheduler

Kent Hudson

Kent Hudson

·

24 minuten lesen

Telegram News Bot in Python (2026): aiogram + APScheduler

Building a Telegram News Bot with Python (2026 Edition)

A Telegram news bot is a Python program that fetches articles from a news API, filters them by topic or sentiment, and posts them to a Telegram chat or channel automatically. In 2026 the fast way to build one is aiogram 3.27 for the async Telegram client, APScheduler for in-process scheduling, and a news API that exposes sentiment and entities so you can post curated streams instead of a firehose.

Key takeaways

  • Use aiogram 3.27 (async) over python-telegram-bot for channel auto-posting bots above ~50 concurrent users.
  • Filter the news API response by sentiment.overall.polarity and entities[] so the channel posts curated content, not a firehose.
  • APScheduler's AsyncIOScheduler runs inside the aiogram event loop — one process for polling, fetching, and scheduled posting.
  • Ship as a multi-stage Python 3.12 Docker image with restart: unless-stopped; Fly.io, Railway, or a $5/month VPS all work.

This tutorial walks through a production-shaped channel bot in about 100 lines: it pulls articles every 15 minutes, drops anything that doesn't match the sentiment and entity filter, deduplicates by article ID, and survives a container restart. By the end you'll have a Dockerized service you can drop on any VPS.

What you'll build

  • An async Telegram bot that posts news to a channel automatically
  • Sentiment + entity filter (e.g., only post negative articles mentioning Tesla)
  • APScheduler running inside the aiogram event loop — no cron, no second process
  • Deduplication by article ID and retry on Telegram rate limits
  • A multi-stage Dockerfile with restart: unless-stopped you can ship today

aiogram vs python-telegram-bot — quick decision

This is the question every Python dev asks first. Both libraries are maintained in 2026; the right pick depends on workload.

Criterionaiogram 3.27python-telegram-bot 21.x
Concurrency modelAsync (asyncio) onlyAsync since v20; sync wrapper still available
Latency at 100 concurrent updates~80 ms p95~600 ms p95 (sync) / ~120 ms (async)
Channel auto-post + scheduler in one processNative fit (AsyncIOScheduler)Works but more boilerplate
FSM, middleware, filtersFirst-classFirst-class
Best forChannel/group bots, news/notification streams, >500 usersPersonal bots, command-driven workflows, gradual sync→async migration
Community trajectory in 2026Faster moving, channel-bot focusLarger ecosystem, more legacy examples

Unlike python-telegram-bot, aiogram has been async-only since v3, which means it shares a single event loop with AsyncIOScheduler and httpx.AsyncClient — no bridging, no thread pools, no callback wrapping. Practical rule: under 50 concurrent updates either library is fine; above 500, or for any channel auto-poster, pick aiogram. We'll use aiogram for the rest of this tutorial.

Prerequisites

  • Python 3.10 or newer (3.12 recommended)
  • A Telegram bot token from @BotFather
  • A Telegram channel where your bot is added as an administrator with "Post messages" permission
  • An APITube API key from apitube.io — free tier includes sentiment and entities on every article (disclosure: I work on APITube)
  • Familiarity with async/await — if await client.get(...) looks alien, skim the Python asyncio tutorial first

How to build a Telegram news bot in Python (6 steps)

  1. Get a bot token from BotFather
  2. Set up the project and install dependencies
  3. Fetch news from APITube (async)
  4. Filter by sentiment and entities
  5. Schedule channel posts with APScheduler
  6. Add /start, /subscribe, /filter commands

Step 1 — Get a bot token from BotFather

Open Telegram, message @BotFather, send /newbot, pick a name and a username. You'll get a token that looks like 8123456789:AAH.... Save it.

Then create a channel (or use an existing one), open its settings → Administrators → Add Administrator → search your bot's username → grant "Post messages". Get the channel ID by forwarding any channel message to @JsonDumpBot — you want the chat.id, which for channels is a negative number like -1001234567890.

Step 2 — Set up the project

mkdir telegram-news-bot && cd telegram-news-bot
python -m venv .venv && source .venv/bin/activate
pip install aiogram==3.27.0 apscheduler==3.10.4 httpx==0.27.0 python-dotenv==1.0.1

Create .env:

BOT_TOKEN=8123456789:AAH...
CHANNEL_ID=-1001234567890
APITUBE_KEY=your_apitube_key

Step 3 — Fetch news from APITube

APITube's GET /v1/news/everything endpoint takes a search title, language, category, and date range, and returns articles enriched with sentiment scores and named entities. Auth goes in the X-API-Key header.

Quick sanity check from the shell:

curl "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2" \
  -H "X-API-Key: $APITUBE_KEY"

Response — one full article object, every field APITube returns per result:

{
  "status": "ok",
  "limit": 2,
  "path": "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2&page=1",
  "page": 1,
  "has_next_pages": true,
  "next_page": "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2&page=2",
  "has_previous_page": false,
  "previous_page": "",
  "export": {
    "json": "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2&page=1&export=json",
    "xlsx": "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2&page=1&export=xlsx",
    "csv": "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2&page=1&export=csv",
    "tsv": "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2&page=1&export=tsv",
    "xml": "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2&page=1&export=xml",
    "rss": "https://api.apitube.io/v1/news/everything?title=Tesla&language.code=en&per_page=2&page=1&export=rss"
  },
  "request_id": "5978d89c-46f6-4ab4-b65c-8b4a6000d4ac",
  "results": [
    {
      "id": 982451653,
      "href": "https://example.com/tesla-recall",
      "published_at": "2026-05-02T08:14:00Z",
      "title": "Tesla recalls 1.2M vehicles over autopilot flaw",
      "description": "The recall covers most Model 3 and Y units...",
      "body": "Tesla on Friday recalled roughly 1.2 million vehicles over a defect in its autopilot driver-assistance system. The recall covers most Model 3 and Model Y units built since 2023.",
      "body_html": "<p>Tesla on Friday recalled roughly 1.2 million vehicles over a defect in its autopilot driver-assistance system. The recall covers most Model 3 and Model Y units built since 2023.</p>",
      "language": "en",
      "author": {
        "id": 4521,
        "name": "Jane Doe"
      },
      "image": "https://example.com/images/tesla-recall.jpg",
      "categories": [
        {
          "id": 211,
          "name": "automotive",
          "score": 0.91,
          "taxonomy": "iptc_mediatopics",
          "links": {
            "self": "https://api.apitube.io/v1/news/category/iptc_mediatopics/medtop:20000266"
          }
        }
      ],
      "topics": [
        {
          "id": "automotive.recalls",
          "name": "Vehicle Recalls",
          "score": 0.34,
          "links": {
            "self": "https://api.apitube.io/v1/news/topic/automotive.recalls"
          }
        }
      ],
      "industries": [
        {
          "id": 88,
          "name": "Automotive",
          "links": {
            "self": "https://api.apitube.io/v1/news/industry/88"
          }
        }
      ],
      "entities": [
        {
          "id": 8821,
          "name": "Tesla",
          "type": "organization",
          "links": {
            "self": "https://api.apitube.io/v1/news/entity/8821",
            "wikipedia": "https://en.wikipedia.org/wiki/Tesla,_Inc.",
            "wikidata": "https://www.wikidata.org/wiki/Q478214"
          },
          "frequency": 5,
          "title": {
            "pos": [
              {"start": 0, "end": 5}
            ]
          },
          "body": {
            "pos": [
              {"start": 0, "end": 5},
              {"start": 142, "end": 147}
            ]
          },
          "metadata": {
            "name": "Tesla",
            "type": "business",
            "country": {"code": "US", "name": "United States"},
            "description": "Electric vehicle manufacturer"
          }
        },
        {
          "id": 19233,
          "name": "Model 3",
          "type": "product",
          "links": {
            "self": "https://api.apitube.io/v1/news/entity/19233"
          },
          "frequency": 2,
          "title": {
            "pos": []
          },
          "body": {
            "pos": [
              {"start": 58, "end": 65}
            ]
          },
          "metadata": {}
        }
      ],
      "source": {
        "id": 311,
        "domain": "reuters.com",
        "home_page_url": "https://www.reuters.com",
        "type": "news",
        "bias": "center",
        "rankings": {
          "opr": 9
        },
        "location": {
          "country_name": "United States",
          "country_code": "us"
        },
        "favicon": "https://www.reuters.com/favicon.ico"
      },
      "sentiment": {
        "overall": {"score": -0.62, "polarity": "negative"},
        "title": {"score": -0.55, "polarity": "negative"},
        "body": {"score": -0.64, "polarity": "negative"}
      },
      "summary": [
        {
          "sentence": "Tesla recalled roughly 1.2 million vehicles over an autopilot defect.",
          "sentiment": {"score": -0.6, "polarity": "negative"}
        }
      ],
      "keywords": ["Tesla", "recall", "autopilot", "Model 3"],
      "links": [
        {"url": "https://example.com/tesla-recall-followup", "type": "link"}
      ],
      "media": [
        {"url": "https://example.com/images/tesla-recall.jpg", "type": "image"}
      ],
      "story": {
        "id": 3019419820,
        "uri": "https://api.apitube.io/v1/news/story/3019419820"
      },
      "shares": {
        "total": 412,
        "facebook": 220,
        "twitter": 150,
        "reddit": 42
      },
      "is_duplicate": false,
      "is_free": true,
      "is_breaking": true,
      "read_time": 2,
      "sentences_count": 8,
      "paragraphs_count": 4,
      "words_count": 320,
      "characters_count": 1820
    }
  ]
}

The bot only touches a handful of these — title, description, href, source.domain, sentiment.overall.polarity, and entities[].name — but everything else (topics, industries, source bias, per-sentence summary, share counts) is there if you want richer posts later.

In Python, async httpx:

import httpx, os

API_URL = "https://api.apitube.io/v1/news/everything"

async def fetch_news(query: str) -> list[dict]:
    params = {
        "title": query,
        "language.code": "en",
        "per_page": 20,
        "sort.by": "published_at",
        "sort.order": "desc",
    }
    headers = {"X-API-Key": os.environ["APITUBE_KEY"]}
    async with httpx.AsyncClient(timeout=15) as client:
        r = await client.get(API_URL, params=params, headers=headers)
        r.raise_for_status()
        return r.json().get("results", [])

Step 4 — Filter by sentiment and entities

This is the step that turns a firehose into a curated channel. Drop articles that don't match the configured polarity, and keep only ones whose entities include at least one watched name. None of the popular tutorials do this — they post everything they fetch.

WATCHED_ENTITIES = {"Tesla", "SpaceX", "OpenAI"}
WANTED_POLARITY = {"negative"}  # use {"positive", "negative", "neutral"} to allow all

def passes_filter(article: dict) -> bool:
    polarity = article.get("sentiment", {}).get("overall", {}).get("polarity")
    if polarity not in WANTED_POLARITY:
        return False
    names = {e.get("name") for e in article.get("entities", [])}
    return bool(names & WATCHED_ENTITIES)

A 30-second filter, but it's the difference between a bot that posts 200 articles a day and one that posts 6 useful ones.

Step 5 — Schedule channel posts with APScheduler

AsyncIOScheduler runs inside the same event loop aiogram is using, so the whole bot is one process. Dedup posted IDs in an in-memory set; for persistence across restarts, swap in Redis or SQLite.

from apscheduler.schedulers.asyncio import AsyncIOScheduler
from aiogram.exceptions import TelegramRetryAfter
import asyncio, html

posted_ids: set[int] = set()

async def post_news(bot, channel_id: int):
    articles = await fetch_news("Tesla OR SpaceX OR OpenAI")
    for art in articles:
        if art["id"] in posted_ids or not passes_filter(art):
            continue
        text = (
            f"<b>{html.escape(art['title'])}</b>\n"
            f"{html.escape(art['description'])}\n\n"
            f"{art['source']['domain']} · "
            f"sentiment: {art['sentiment']['overall']['polarity']}\n"
            f"<a href=\"{art['href']}\">Read more</a>"
        )
        try:
            await bot.send_message(channel_id, text, disable_web_page_preview=False)
            posted_ids.add(art["id"])
        except TelegramRetryAfter as e:
            await asyncio.sleep(e.retry_after)

The TelegramRetryAfter catch is what keeps the bot alive when Telegram throttles you for posting too fast — it sleeps the exact requested interval, then the next scheduler tick retries.

Step 6 — Commands and main loop

Three small commands so subscribers can interact with the bot directly:

  • /start — welcome message
  • /subscribe — explain that the bot auto-posts to the channel
  • /filter — show current filter config

The dispatcher wires them up, and start_polling runs alongside the scheduler.

Complete code (bot.py)

import asyncio, html, logging, os
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.exceptions import TelegramRetryAfter
from aiogram.filters import Command, CommandStart
from aiogram.types import Message
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger
from dotenv import load_dotenv
import httpx

load_dotenv()
TOKEN = os.environ["BOT_TOKEN"]
CHANNEL_ID = int(os.environ["CHANNEL_ID"])
APITUBE_KEY = os.environ["APITUBE_KEY"]
API_URL = "https://api.apitube.io/v1/news/everything"

WATCHED_ENTITIES = {"Tesla", "SpaceX", "OpenAI"}
WANTED_POLARITY = {"negative"}
posted_ids: set[int] = set()

dp = Dispatcher()

async def fetch_news(query: str) -> list[dict]:
    params = {
        "title": query,
        "language.code": "en",
        "per_page": 20,
        "sort.by": "published_at",
        "sort.order": "desc",
    }
    headers = {"X-API-Key": APITUBE_KEY}
    async with httpx.AsyncClient(timeout=15) as client:
        r = await client.get(API_URL, params=params, headers=headers)
        r.raise_for_status()
        return r.json().get("results", [])

def passes_filter(article: dict) -> bool:
    polarity = article.get("sentiment", {}).get("overall", {}).get("polarity")
    if polarity not in WANTED_POLARITY:
        return False
    names = {e.get("name") for e in article.get("entities", [])}
    return bool(names & WATCHED_ENTITIES)

async def post_news(bot: Bot):
    articles = await fetch_news("Tesla OR SpaceX OR OpenAI")
    for art in articles:
        if art["id"] in posted_ids or not passes_filter(art):
            continue
        text = (
            f"<b>{html.escape(art['title'])}</b>\n"
            f"{html.escape(art.get('description') or '')}\n\n"
            f"{art['source']['domain']} · "
            f"sentiment: {art['sentiment']['overall']['polarity']}\n"
            f"<a href=\"{art['href']}\">Read more</a>"
        )
        try:
            await bot.send_message(CHANNEL_ID, text)
            posted_ids.add(art["id"])
            await asyncio.sleep(1)
        except TelegramRetryAfter as e:
            await asyncio.sleep(e.retry_after)
        except Exception:
            logging.exception("send_message failed for %s", art["id"])

@dp.message(CommandStart())
async def cmd_start(m: Message):
    await m.answer("News bot online. Curated negative-sentiment news on Tesla, SpaceX, OpenAI.")

@dp.message(Command("subscribe"))
async def cmd_subscribe(m: Message):
    await m.answer("Posts go to the channel automatically every 15 minutes.")

@dp.message(Command("filter"))
async def cmd_filter(m: Message):
    await m.answer(
        f"Polarity: {sorted(WANTED_POLARITY)}\nEntities: {sorted(WATCHED_ENTITIES)}"
    )

async def main():
    logging.basicConfig(level=logging.INFO)
    bot = Bot(TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
    scheduler = AsyncIOScheduler()
    scheduler.add_job(post_news, IntervalTrigger(minutes=15), args=[bot])
    scheduler.start()
    await dp.start_polling(bot)

if __name__ == "__main__":
    asyncio.run(main())

Run it:

python bot.py

The first scheduler tick fires 15 minutes after start. To trigger immediately, add next_run_time=datetime.utcnow() to add_job.

Deploy with Docker

A multi-stage Python 3.12 slim image keeps the final container small and the build deterministic.

Dockerfile:

FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
COPY bot.py .
CMD ["python", "-u", "bot.py"]

requirements.txt:

aiogram==3.27.0
apscheduler==3.10.4
httpx==0.27.0
python-dotenv==1.0.1

docker-compose.yml:

services:
  bot:
    build: .
    env_file: .env
    restart: unless-stopped

For free or near-free hosting in 2026: Fly.io's hobby tier handles a single bot container fine; Railway gives you logs and one-click redeploys; or grab a $5/month VPS (Hetzner, Vultr, DigitalOcean) and run docker compose up -d. Whatever you pick, restart: unless-stopped ensures a crash doesn't take the bot offline overnight.

FAQ

How do I make a Telegram news bot in Python?

Register a bot with @BotFather, install aiogram 3.27 and a news client like httpx, fetch articles from a news API such as APITube, filter the response, and post each item to a Telegram chat or channel using bot.send_message. Wrap the fetch-and-post step in APScheduler to make it run on an interval.

Which library is better, aiogram or python-telegram-bot?

Unlike python-telegram-bot, aiogram is async-only since v3, which means it pairs cleanly with AsyncIOScheduler for channel auto-posting in a single event loop. For news/channel bots above ~50 concurrent users, pick aiogram 3.27. For command-driven personal bots, sync-codebase teams, or gradual migration paths, python-telegram-bot 21.x remains a solid choice. Both are actively maintained in 2026.

Can a Telegram bot post to a channel automatically?

Yes. Add the bot to the channel as an administrator with the "Post messages" permission, grab the channel's numeric chat ID, and call bot.send_message(chat_id, text) from a scheduled task. APScheduler's AsyncIOScheduler runs inside the bot's event loop, so one process handles both polling and scheduled posting.

Is python-telegram-bot still maintained in 2026?

Yes — python-telegram-bot is still actively maintained. Version 21.x ships an async API and ongoing fixes; the project sees regular releases on GitHub. The "is it maintained" question gets asked because the v13 sync API is deprecated, but v20 onwards is async and current.

Try it

Clone the snippets above, drop them in a folder, fill .env, docker compose up -d. APITube's free tier returns sentiment and entities on every article — get a key at apitube.io and replace the placeholder in step 3. If you want to filter on category instead of entities, read article["categories"] and match on each category's name (e.g. keep only articles whose category name contains "technology").

APITube - News API

Verwandte Artikel

Build a Django News Portal in 2026: Full Stack Tutorial
Developer Guides

Build a Django News Portal in 2026: Full Stack Tutorial

Build a Django news portal: Celery beat ingestion, Redis cache, HTMX infinite scroll, Postgres full-text search. Real news API, runnable Django 5 code.

React News Dashboard Tutorial 2026: SSE + TypeScript
Developer Guides

React News Dashboard Tutorial 2026: SSE + TypeScript

Build a real-time React news dashboard with TypeScript and Server-Sent Events. Full code, 429-safe fetch hook, sentiment filters, Vercel deploy.

Best Financial News API for Trading 2026: 5 Compared
Insights

Best Financial News API for Trading 2026: 5 Compared

Five financial news APIs scored on latency, ticker-tagging, sentiment, backtesting archive, and trading-event feeds. 2026 fintech-focused comparison.

NewsAPI.org Alternative 2026: Why Devs Pick APITube
Insights

NewsAPI.org Alternative 2026: Why Devs Pick APITube

NewsAPI.org alternative for 2026 — TOS quote, real migration code, 12-month TCO, and when NewsAPI is still fine. APITube vs NewsAPI.org, straight.

Wir verwenden Cookies

Indem Sie auf "Akzeptieren" klicken, stimmen Sie der Speicherung von Cookies auf Ihrem Gerät zu Funktions- und Analysezwecken zu.