// tg-parser-1.md | 28.07.2026

Telegram парсер участников каналов

Консольный инструмент на Telethon: инвентаризация участников Telegram-групп с чекпоинтами и обходом FloodWait.

Задача

Собрать участников группы из истории сообщений и вытащить доступные поля профиля в Excel и JSON. Работает через официальный MTProto-клиент telethon, без браузера, прокси и разбора веб-интерфейса.

Как делал

Главное решение — две фазы вместо одного прохода. Из истории сообщений берутся только sender_id, и это почти бесплатный запрос. Дорогие GetFullUserRequest делаются уже по списку уникальных ID, поэтому вместо обращения на каждое сообщение уходит обращение на уникального человека: на группе в десятки тысяч сообщений это сжимает десятки тысяч запросов до сотен.

async for msg in client.iter_messages(entity, **msg_kwargs):
    if msg.sender_id and msg.sender_id > 0:   # каналы дают отрицательный, анонимы — 0
        user_ids.add(msg.sender_id)
    if min_msg_id is None or msg.id < min_msg_id:
        min_msg_id = msg.id      # самый старый id прохода — точка отсчёта для следующего
    count += 1

Знак sender_id отсекает каналы и анонимных авторов без отдельной проверки. min_msg_id становится offset_id следующего прохода: обход уходит вглубь истории, а не крутится по верхней тысяче сообщений. Цель считается как limit × passes, и если она уже набрана, скрипт вообще не начинает читать.

Отдельная особенность Telegram: phone приходит из GetFullUserRequest только для тех, кто уже в контактах. У остальных поле пустое, поэтому телефоны на выходе — редкая строка, а не основная колонка. Это заложено в дизайн: скрипт не пытается обойти ограничение, а просто пишет пустую ячейку и отдаёт пустую ячейку.

Авторизация — файл сессии, который telethon создаёт при первом входе. Дальше инструмент стартует сразу, без кода из Telegram.

Задержки не фиксированные, а случайные в диапазоне: random.uniform(7, 12) между запросами данных и random.uniform(4, 6) каждые 200 сообщений. Ровный ритм — тот самый рисунок, по которому flood-контроль Telegram распознаёт автоматизацию. При FloodWait скрипт не паникует, а пишет чекпоинт и выходит: перезапуск продолжит с того же места.

delay = random.uniform(*DELAY_USERS)   # 7-12 секунд, а не фиксированные 7
...
if idx % CHECKPOINT_EVERY == 0:
    if SAVE_JSON:
        with open(users_path, 'w', encoding='utf-8') as f:
            json.dump(users, f, ensure_ascii=False, indent=2)
await asyncio.sleep(delay)

Прогресс переживает перезапуск двумя файлами: _ids.json хранит собранные ID, _meta.json — offset, число проходов и сколько сообщений уже прочитано. При следующем запуске из них собирается remaining, то есть докачиваются только те, кого ещё нет. Без этого группа в десять тысяч сообщений после каждого Flood разбиралась бы заново.

if os.path.exists(users_path):
    with open(users_path, 'r', encoding='utf-8') as f:
        users = json.load(f)
    done_ids = {u['id'] for u in users}

remaining = [uid for uid in ids_list if uid not in done_ids]
if not remaining:
    print(f"  [2] Все {len(ids_list)} уже обработаны!")

Телефон в Excel уводится из числового формата: без number_format = '@' таблица переписывает номер в число и теряет вид, а без плюса он читается как локальный. Имя листа дополнительно чистится от ?*/\[]: и режется до 31 символа — ровно то, что Excel не принимает.

if field == 'phone':
    val = u.get(field, '')
    if val and not val.startswith('+'):
        val = '+' + val
...
if field == 'phone' and val:
    cell.number_format = '@'      # иначе Excel перепишет номер в число

ws.title = safe_name(group_name)  # re.sub(r'[?*/\\[\]:]', '', text)[:31]

Лист «Сводка» собирается функцией, где у строки три состояния: false — не выводить, строка — своя подпись, иначе подпись по умолчанию. В неё же подтягивается total_scanned из _meta.json, поэтому видно, сколько сообщений реально прочитано, а не сколько найдено ID.

Конфиг написан не как JSON, а как JSONC: комментарии и висячие запятые вырезаются перед json.loads, так что каждое поле задержки описано прямо над своим значением.

if stripped.startswith('#') or stripped.startswith('//'):
    continue
...
text = re.sub(r',\s*([}\]])', r'\1', text)   # висячие запятые
return json.loads(text)

Итог

Консольный инструмент на telethon и openpyxl: две фазы, чекпоинты, случайные задержки, восстановление после FloodWait. Набор полей, фильтры, лимиты, задержки и состав сводки задаются одним JSONC-конфигом — под другую группу скрипт не правится, а просто перенастраивается.

// похожие проекты

Ещё работы

// проект 2026
Скриншот главной страницы лединга AROMA

Aroma. Лендинг кофейни

Одностраничный лендинг кофейни: атмосфера, меню с фильтрами и каруселью, форма бронирования с модалкой подтверждения.

// проект 2026
Скриншот главной страницы сайта xyjear

Xyjear. Сайт-портфолио

Сайт-портфолио на своей теме WordPress с нуля: живой терминал, скрамбл-заголовок, CSS одним ответом.

// проект 2026
Скриншот страницы сайта YTWrapped

YT-wrapped. Аналитика YouTube-каналов

Аналитика YouTube-каналов: поиск, разбор последних 50 видео, вовлечённость, частота загрузок и рейтинг роликов.

// проект 2026
Скриншот главной страницы лендинга пекарни "круассан"

«Круассан». Лендинг для пекарни

Лендинг пекарни: меню выпечки, отзывы, контакты и форма заказа торта.

// проект 2026
Нет изображения

Парсер публикаций ВКонтакте

Парсер для VK: находит подходящие посты в поиске новостей и ставит их в предложку своих групп, с защитой от повторов.

// проект 2026
Скриншот главной страницы лендинга ИГРАЙ-КА

«Играй-ка». Лендинг магазина игрушек

Одностраничный лендинг магазина детских игрушек: витрина, категории, отзывы и форма заявки.

> все проекты