Как безопасно парсить 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 вычисляет ботов)
Красные флаги: от самых опасных к безобидным
- 🔴 ResolveUsername массово = самое опасное. Лимит крошечный, бан быстрый
- 🔴 Спам-рассылка незнакомым = мгновенный бан
- 🟡 GetHistory без пауз = FloodWait → потом бан
- 🟡 get_entity() по username = скрытый ResolveUsername!
- 🟢 get_entity() по числовому ID = безопасно, берет из кэша
- 🟢 iter_messages() с паузами = безопасно
- 🟢 get_dialogs() = относительно безопасно
Fingerprinting: как Telegram понимает, что ты бот
Telegram анализирует не только количество запросов, но и паттерн поведения:
- Регулярные интервалы. Человек не делает запросы ровно каждые 2 секунды. Рандомизируй задержки
- Время суток. Парсинг в 3 ночи = подозрительно. Работай в «человеческие» часы (10:00-22:00)
- Device info. Telethon по умолчанию отправляет device_model, system_version. Если не настроить, Telegram видит «Telethon 1.x» вместо «Samsung Galaxy S24»
- Только чтение без отправки. Реальный пользователь иногда отправляет сообщения, ставит реакции. Аккаунт который только читает месяцами = робот
- IP адрес. Датацентровый IP + MTProto = красный флаг. Residential прокси сильно снижают риск
Безопасный подход: архитектура парсинга
Ключевой инсайт, который стоил нам двух банов: для парсинга СВОИХ чатов вообще НЕ нужен 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') опасно.
Безопасные задержки (рекомендации сообщества)
| Действие | Задержка |
|---|---|
| Между запросами GetHistory | 1-3 секунды (рандом) |
| Между чатами | 5-10 секунд |
| Между сессиями парсинга | 30-60 минут |
| При FloodWaitError | wait_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 |
|---|---|---|---|---|
| Язык | Python | Python | C++/bindings | JavaScript |
| Удобство | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| FloodWait handling | Авто | Авто | Авто | Ручное |
| Документация | Отличная | Хорошая | Средняя | Средняя |
| SaaS-масштаб | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
Вердикт: Telethon для Python-стека. Самая зрелая экосистема, лучшая документация, встроенный flood wait handling. Если у тебя JS-стек, GramJS работает, но FloodWait придется обрабатывать руками.
Архитектура для SaaS: мультиаккаунтный парсинг
Если строишь SaaS где пользователи подключают свои Telegram-аккаунты для анализа чатов, правила другие:
- Каждый клиент = своя сессия, свой API ID. Клиент создает приложение на my.telegram.org самостоятельно. Один API ID на множество аккаунтов = красный флаг
- Rate limiter на уровне приложения. Глобальный семафор: не больше N запросов в секунду суммарно по всем аккаунтам с одного сервера
- Residential прокси. Один IP на аккаунт. В стране номера телефона. Датацентровые IP + MTProto = бан
- Прогрев аккаунтов. Не начинай парсинг сразу после подключения. Дай аккаунту «пожить» в системе 2-3 дня с минимальной активностью
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, пройдись по этому списку перед запуском:
- ☐ Аккаунт старше 2-3 месяцев (не свежерег)
- ☐ Cool-down минимум 72 часа после последнего бана
- ☐ device_model, system_version, app_version настроены (не дефолтный Telethon)
- ☐ Используешь ТОЛЬКО числовые ID чатов (не username)
- ☐ iter_dialogs() вызван один раз, каталог в БД
- ☐ Задержки рандомизированы (не ровные интервалы)
- ☐ 1-3 сек между запросами, 5-10 сек между чатами
- ☐ Максимум 10-15 чатов и 2000 сообщений за сессию
- ☐ Парсинг только в «человеческие» часы (10:00-22:00)
- ☐ FloodWaitError обрабатывается: sleep(wait_time × 1.5) + стоп сессии
- ☐ IP не датацентровый (residential proxy или домашний)
- ☐ На аккаунте есть «живая» активность (не только чтение)
- ☐ Для SaaS: один API ID на аккаунт (клиент создает свой)
Источники и дополнительное чтение
- Telethon FAQ: FloodWait
- Telethon: Entities и get_input_entity
- Telethon #557: FloodErrors and how to avoid them
- RSSHub #20454: ResolveUsername rate limited
- Habr: Парсинг Telegram каналов + LLM
- Habr: Автоматизация чтения Telegram-каналов (фев 2026)
- Telegram Limits (все лимиты)
DeathScore помогает стартапам оценить риски до запуска.
Проверить свою идею →