🌐 EN
Безопасный парсинг Telegram-чатов
⏱ 15 мин

Как безопасно парсить Telegram-чаты без бана

Rate limits MTProto, конкретные цифры по лимитам, рабочий код на Telethon и архитектура для SaaS-продуктов. Из боевого опыта: два бана, ноль потерь.

Проблема: почему аккаунты летят в бан

Ты решил вытянуть историю из своих Telegram-чатов. Написал скрипт на Telethon, запустил, получил первые 1000 сообщений... и бан на 5 часов. Перезапустил через сутки, аккуратнее. Бан на 24 часа. Еще одна попытка = permanent ban. Аккаунт удален.

Это не теория. Это реальный сценарий, который мы прошли на аккаунте с API ID 38884228. Два бана подряд. Третий мог стать последним.

🔴 Эскалация наказаний в Telegram: FloodWait 5-30 сек → FloodWait 5-60 мин → бан 5-24 часа → PERMANENT BAN (аккаунт удален навсегда). После двух банов следующий с высокой вероятностью финальный.

Главная причина нашего бана: contacts.ResolveUsername. Этот метод резолвит юзернейм (@channel_name) в числовой ID. И у него дневной лимит всего ~50-200 запросов. Мы вызывали его сотни раз, даже не подозревая.

Дело в том, что client.get_messages('@channel_name') внутри вызывает ResolveUsername каждый раз. Каждый вызов get_entity() по юзернейму тоже. А если у тебя 50 чатов и ты дергаешь каждый по имени... всё, лимит исчерпан за минуты.

Rate limits MTProto: конкретные цифры

Официальной документации по лимитам у Telegram нет. Это сознательная политика: не публиковать точные числа, чтобы спамеры не могли вычислить идеальный темп. Но из опыта сообщества (GitHub issues, Reddit, Stack Overflow) картина такая:

Метод Безопасный лимит Источник
contacts.ResolveUsername ~50-200 запросов/день (!) GitHub RSSHub #20454, SO
messages.GetHistory ~30 запросов/мин (по 100 сообщений) Сообщество Telethon
messages.GetDialogs Без явного лимита, FloodWait при >100 за раз Telethon docs
Общий rate limit ~30 запросов/сек (глобально) TDLib issues #3034
get_entity() по username ~200/день (= ResolveUsername) SO, практика
get_entity() по числовому ID Без ограничений (из кэша) Telethon docs

⚠️ Ключевой инсайт: Лимиты динамические. Они зависят от «доверия» к аккаунту. Новый аккаунт получит FloodWait быстрее, чем аккаунт с двухлетней историей. Точных цифр нет намеренно (Telethon #557).

Что триггерит баны (и как Telegram вычисляет ботов)

Красные флаги: от самых опасных к безобидным

Fingerprinting: как Telegram понимает, что ты бот

Telegram анализирует не только количество запросов, но и паттерн поведения:

Безопасный подход: архитектура парсинга

Ключевой инсайт, который стоил нам двух банов: для парсинга СВОИХ чатов вообще НЕ нужен ResolveUsername. Вот правильная схема:

Этап 1: Bootstrap (один раз)

Вызвать iter_dialogs() = получить список всех чатов с числовыми ID. Один вызов. Без ResolveUsername. Сохранить каталог в БД: chat_id, title, type, last_message_id.

Этап 2: Инкрементальный парсинг (по расписанию)

Для каждого чата по приоритету: iter_messages(chat_id, min_id=last_saved_id). Только числовые ID. Паузы 2-3 сек между batch'ами, 5-10 сек между чатами. Максимум 10-15 чатов и 2000 сообщений за сессию.

Этап 3: Real-time (event handler)

Подключить events.NewMessage для новых сообщений. Push-модель. Нулевые rate limits. Это самый безопасный способ получать данные.

✅ Золотое правило: НИКОГДА не резолвить username программно. Используй числовые ID из кэша/каталога. client.get_messages(-1001234567890) безопасно. client.get_messages('@channel_name') опасно.

Безопасные задержки (рекомендации сообщества)

ДействиеЗадержка
Между запросами GetHistory1-3 секунды (рандом)
Между чатами5-10 секунд
Между сессиями парсинга30-60 минут
При FloodWaitErrorwait_time × 1.5 (не меньше указанного!)
Максимум сообщений за сессию1000-3000
Максимум чатов за сессию10-20

Рабочий код: SafeTelegramParser

Вот production-ready класс который мы используем. Ключевые моменты: маскировка device info, рандомные задержки, автоматический stop при FloodWait.

import asyncio
import random
from telethon import TelegramClient, events
from telethon.errors import FloodWaitError

class SafeTelegramParser:
    def __init__(self, session, api_id, api_hash):
        self.client = TelegramClient(
            session, api_id, api_hash,
            device_model="Samsung Galaxy S24",
            system_version="Android 15",
            app_version="11.5.2",
            lang_code="ru",
            system_lang_code="ru-RU"
        )
        self.chat_catalog = {}

    async def bootstrap_dialogs(self):
        """Получить все чаты ОДИН РАЗ"""
        async for dialog in self.client.iter_dialogs():
            self.chat_catalog[dialog.id] = {
                'title': dialog.title or dialog.name,
                'type': ('channel' if dialog.is_channel
                         else 'group' if dialog.is_group
                         else 'user'),
                'last_msg_id': 0,
            }
        return self.chat_catalog

    async def parse_chat(self, chat_id, limit=100, min_id=0):
        """Парсить историю одного чата по числовому ID"""
        messages = []
        try:
            async for msg in self.client.iter_messages(
                chat_id, limit=limit, min_id=min_id
            ):
                messages.append({
                    'id': msg.id,
                    'date': msg.date,
                    'text': msg.text,
                    'sender_id': msg.sender_id,
                })
                if len(messages) % 100 == 0:
                    await asyncio.sleep(random.uniform(2.0, 4.0))

        except FloodWaitError as e:
            print(f"FloodWait: {e.seconds}s. STOP.")
            await asyncio.sleep(e.seconds * 1.5)
            return messages, True  # flood detected

        return messages, False

    async def safe_session(self, max_chats=10, max_msgs=2000):
        """Безопасная сессия парсинга"""
        total = 0
        for chat_id, info in list(self.chat_catalog.items())[:max_chats]:
            if total >= max_msgs:
                break

            msgs, flood = await self.parse_chat(
                chat_id,
                limit=min(100, max_msgs - total),
                min_id=info['last_msg_id']
            )
            total += len(msgs)
            if msgs:
                info['last_msg_id'] = max(m['id'] for m in msgs)
            if flood:
                break

            await asyncio.sleep(random.uniform(5.0, 10.0))

        return total

    async def setup_realtime(self, handler):
        """Real-time: push без rate limits"""
        @self.client.on(events.NewMessage())
        async def on_msg(event):
            if event.chat_id in self.chat_catalog:
                await handler(event)

Маскировка device info

По умолчанию Telethon отправляет что-то вроде «Telethon 1.x / Python». Telegram это видит. Замена на реалистичное устройство снижает шансы на автоматическое флагирование:

# Плохо: Telegram видит "Telethon"
client = TelegramClient('session', api_id, api_hash)

# Хорошо: Telegram видит "обычный Android"
client = TelegramClient(
    'session', api_id, api_hash,
    device_model="Samsung Galaxy S24",
    system_version="Android 15.0",
    app_version="11.5.2 (5592)",
    lang_code="ru",
    system_lang_code="ru-RU"
)

Библиотеки: Telethon vs Pyrogram vs TDLib

Все MTProto-библиотеки работают через один и тот же протокол. Бан зависит от поведения, не от библиотеки. Но удобство отличается:

Параметр Telethon Pyrogram TDLib GramJS
ЯзыкPythonPythonC++/bindingsJavaScript
Удобство⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
FloodWait handlingАвтоАвтоАвтоРучное
ДокументацияОтличнаяХорошаяСредняяСредняя
SaaS-масштаб⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Вердикт: Telethon для Python-стека. Самая зрелая экосистема, лучшая документация, встроенный flood wait handling. Если у тебя JS-стек, GramJS работает, но FloodWait придется обрабатывать руками.

Архитектура для SaaS: мультиаккаунтный парсинг

Если строишь SaaS где пользователи подключают свои Telegram-аккаунты для анализа чатов, правила другие:

Fallback-стратегии: если парсинг не вариант

Иногда лучше вообще не парсить. Вот альтернативы по приоритету:

🥇 Real-time handler (events.NewMessage)

Бесплатно, без лимитов. Push-модель: Telegram сам присылает новые сообщения. Идеально для «парси с этого момента». Не подходит для истории.

🥈 Telegram Desktop Export

Самый безопасный вариант для истории. Клиент экспортирует чаты в JSON через десктопное приложение. Ноль рисков бана. Минус: ручной процесс, не масштабируется.

🥉 Bot API (если бот в чате)

Если бота добавили админом в чат, он читает сообщения через getUpdates. Без лимитов на чтение. Минус: бота нужно добавить в каждый чат.

Гибридный подход (рекомендуем)

Desktop Export для загрузки истории + real-time handler для новых сообщений. Ноль рисков бана, полное покрытие данных. Это то, к чему мы пришли после двух банов.

💡 Приоритет стратегий: Real-time handler (push) → Desktop Export (для истории) → iter_messages по числовым ID (с паузами) → ResolveUsername (НИКОГДА).

Чеклист: запуск безопасного парсинга

Если ты все-таки решил парсить через MTProto, пройдись по этому списку перед запуском:

Источники и дополнительное чтение

DeathScore помогает стартапам оценить риски до запуска.

Проверить свою идею →