Разбор t.me¶
Страницы¶
| Страница | URL | Парсер |
|---|---|---|
| Лента канала | GET https://t.me/s/{channel}[/{position}] |
parse_channel_page |
| Бесконечная прокрутка | POST https://t.me/s/{channel}?before=… / ?after=… с X-Requested-With: XMLHttpRequest — возвращает JSON-строку с фрагментом HTML |
parse_more_page |
| Главная страница канала | GET https://t.me/{channel} |
parse_preview_page |
Запросы отправляются с заголовками мобильного Chrome. Редиректы не
выполняются: неизвестный канал перенаправляет /s/{channel} на /{channel},
и это возвращается как 404.
Извлечение¶
Разбор реализован на Rust
(rust/telegram-me-native/src/parse)
и выполняется с отпущенным GIL: HTML один раз разбирается html5ever
(соответствующий WHATWG парсер проекта Servo) и обходится CSS-селекторами.
Python лишь декодирует получившийся JSON (pydantic-core) и пишет в лог
пропущенные посты.
Порт на Rust побайтно воспроизводит исходный парсер на Python/selectolax:
селекторы совпадают как с самим узлом, так и с его потомками, текст
склеивается в точности как text() в lexbor, HTML сериализуется заново с
правилами экранирования и порядком атрибутов lexbor, а теги форматирования,
развёрнутые ради простого текста, остаются развёрнутыми при последующих
чтениях того же поддерева — как с unwrap_tags в selectolax, работающим на
месте. Результат зафиксирован снапшотами контракта.
Для каждого поста (.tgme_widget_message_wrap > .tgme_widget_message):
| Поле | Источник |
|---|---|
id |
data-post="<channel>/<id>" |
text |
.tgme_widget_message_text — html — это внутренняя разметка; string — текст с развёрнутыми тегами форматирования и <br> в виде \n |
entities |
регулярные выражения по разметке текста (жирный, курсив, подчёркивание, зачёркивание, код, спойлер, хештеги, ссылки, эмодзи, кастомные эмодзи); варианты без look-ahead выполняются на DFA-движке regex, а три варианта с look-ahead дёшево проверяются и переходят на fancy-regex только при необходимости |
media |
обёртки фото, плееры видео и видеосообщений в кружке, плееры голосовых, стикеры (варианты picture, i, video), изображения превью ссылок |
poll |
вопрос, тип, варианты с процентами и число проголосовавших из .tgme_widget_message_poll |
inline |
каждая .tgme_widget_message_inline_button в inline-клавиатуре |
reply |
.tgme_widget_message_reply: автор, текст, миниатюра, целевой пост по его ссылке |
preview_link |
.tgme_widget_message_link_preview |
reacts |
.tgme_reaction: Telegram Stars, стандартные эмодзи (с URL спрайта), кастомные эмодзи (emoji-id) |
footer |
time[datetime], просмотры, отметка «изменено», подпись автора |
forwarded |
.tgme_widget_message_forwarded_from_name и _author |
Устойчивость¶
- Каждый пост разбирается изолированно. Если с постом что-то не так, он
записывается в лог (
Skipping unparsable post <channel>/<id>) и пропускается; остальная страница возвращается. - После разбора страница проверяется по моделям ответов. Посты, не прошедшие валидацию, отбрасываются тем же способом, так что один странный пост никогда не превращает весь ответ в ошибку.
502возвращается только тогда, когда страница в целом не имеет смысла.
Когда Telegram меняет разметку¶
- Воспроизведите проблему через
uv run python scripts/record_fixtures.py <channel>, чтобы записать новую разметку. - Исправьте парсер в
rust/telegram-me-native/src/parse/и запуститеmake rustиmake native. - Запустите
make snapshotsи просмотрите diff снапшотов — он точно показывает, какие ответы изменились.