Pull to refresh

Парсинг постов из telegram каналов на PHP

Level of difficulty Medium
Reading time 5 min
Views 83K
Привет, Хабр!

Несколько лет назад я начал разрабатывать свой агрегатор контента, что бы упростить свой серфинг в сети. Изначально я парсил только rss, vk и facebook, но в прошлом году решил сделать полный рефакторинг проекта: отказаться от парсинга на клиенте, сделать нормальный back-end, использовать базу данных для хранения данных и расширить список поддерживаемых ресурсов.

Помимо стандартного набора из rss, fb, vk, twitter, instagram, youtube я добавил поддержку произвольных открытых каналов из telegram.

image

Под катом пошаговая инструкция, как парсить любые каналы в telegram без регистрации и смс.

[UPDATE 2019-03-12]
Мой новый пост про парсинг телеграм каналов: habr.com/ru/post/354000. Более качественный код, микросервисы с открытым исходным кодом и новый публичный сервер для тестов.
[UPDATE 2023-10-08]
Важное напоминание: Телеграмм не разрешает парсить аудиторию каналов. Используя серверный клиент можно получить только те данные, которые видны в обычном, оффициальном приложении.


Изначально я предполагал, что парсить каналы можно через популярный BotApi, на который имелось много инструкций в сети. Но оказалось, что для того, что бы бот мог читать какой-то канал — бота надо в этот канал добавить. Для сторонних каналов этот вариант не возможен. Я перешел к чтению мануалов на основной API телеграма.

Через 30 минут изучения документации я был в отчаянии. Все данные у телеграма шифруются, что бы получить что то от их серверов нужно обладать степенью магистра по криптографии… А еще вместо http запросов используется socket, с которым я ранее не сталкивался. Вообщем чистый хардкор и никаких внятных примеров в сети… Это было почти фиаско.

Последней надеждой было найти какое-то готовое решение. И тут, наконец, удача мне улыбнулась. На сайте telegram я наткнулся на ссылку на неофициальный opensource php клиент. Да-да! Можно использовать telegram под php, и там даже есть поддержка звонков! Это чудо называется madelineProto. Оно может подключаться к серверам используя криптографическую магию и отдавать нужные мне данные в виде нормального, человеческого ассоциативного массива.

Я приступил к настройке php клиента.

1. Регистрация своего клиента.

К сожалению, в начале поста я вас обманул и нам всеже потребуется регистрация и смс авторизация в телеграме…

image

Если аккаунт в телеграме уже есть, остается зарегистрировать свое приложение/клиент, и получить ключи для доступа к серверам telegram.

Это стандартная процедура, похожая на аналогичную у соц. сетей для доступа к API. Инструкция для создания своих ключей.

После регистрации клиента нам потребуются только «App api_id» и «App api_hash» со страницы my.telegram.org/apps

2. Установка madelineProto.

Для работы требуется php7, но в Readme написано, что есть способ запустить на php5.6.

С запуском на MacOs с php7 из пакета Mamp, и простеньком хостинге за 150 руб в мес проблем не возникло.

Процесс не хитрый: скачать релиз, установить зависимости через composer и можно приступать к настройке.

Для уменьшения размера я удалил лишние зависимости и оставил только danog, paragonie и phpseclib. На работе клиента это никак не сказалось.

3. Настройка madelineProto и первый запуск.

Все примеры по использованию и настройке описаны в репозитории клиента, но я приведу свой код с комментариями.

На этом этапе потребуется авторизовать новое подключение и ввести код верификации, который придет в ранее авторизованный telegram клиент. Запускать код желательно из консоли (но есть и веб режим). Если настраиваете клиент для другого человека, то нельзя пересылать код авторизации через telegram, иначе он будет аннулирован. Но можно пересылать его в виде скриншота или писать текстом.

Количество авторизаций, которые можно запросить, ограничено. Если что-то не срабатывает — не стоит много раз подряд запускать код, иначе Телеграм заблокирует отправку подтверждений на сутки или более.

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

//мой список настроек минимален, остальные - по умолчанию. Cписок всех значений по умолчанию можно посмотреть в ReadMe клиента на github.
$settings = [
    'app_info' => [ // Эти данные мы получили после регистрации приложения на https://my.telegram.org
        'api_id' => XXXXX,
        'api_hash' => XXXXXXXXXX,
    ],
    'logger' => [ // Вывод сообщений и ошибок
        'logger' => 3, // выводим сообещения через echo
        'logger_level' => 4, // выводим только критические ошибки.
    ],
    //для доступа может потребоваться socks5 прокси
    //если прокси не требуется, то этот блок можно удалить.
    'connection_settings' => [
        'all' => [
            'proxy' => '\SocksProxy',
            'proxy_extra' => [
                'address' => 'xxx.xxx.xxx.xxx',
                'port' => 1234,
                'username' => '',//Можно удалить если логина нет
                'password' => '',//Можно удалить если пароля нет
            ],
        ],
    ],
    'serialization' => [
        'serialization_interval' => 300,
        //Очищать файл сессии от некритичных данных. 
        //Значительно снижает потребление памяти при интенсивном использовании, но может вызывать проблемы
        'cleanup_before_serialization' => true,
    ],
];

$MadelineProto = new \danog\MadelineProto\API('session.madeline', $settings);
$MadelineProto->start();     


В корне проекта будут создан файл «session.madeline», в котором, в бинарном виде, будут храниться данные нашей сессии. При повторном запуске авторизовываться заново не придется. Возобновление сессии работает довольно быстро. У меня на инициализацию клиента уходит около 800 мс на зарубежном сервере.

Для обновления настроек достаточно обновить массив и перезапустить скрипт. Удалять файл сессии не требуется.

4. Получение постов из произвольного открытого telegram канала.

После того как
            
            $data = array(
                    'peer' => '@'.$val['url'], //название_канала, должно начинаться с @, например @breakingmash, все остальные параметры, кроме limit, можно оставить равными 0
                    'offset_id' => $val['offset_id']?:0, 
                    'offset_date' => $val['offset_date']?:0, 
                    'add_offset' => $val['add_offset']?:0, 
                    'limit' => $val['limit']?:10, //Количество постов, которые вернет клиент
                    'max_id' => $val['max_id']?:0, //Максимальный id поста
                    'min_id' => $val['min_id']?:0, //Минимальный id поста - использую для пагинации, при  0 возвращаются последние посты.
                    'hash' => 0
             );

             $response = $MadelineProto->messages->getHistory($data);
             

Так как у меня обновляется много каналов за один раз, то имеет смысл использовать одну и туже сессию, а не тратить по 2 секунды на каждый канал.

После выполнения мы получаем массив с нужным нам количеством сообщений/постов, разбитых по каналам. Так же передаются данные о медиа-вложениях.

Дальше остается сохранить текст поста, при наличии фото/видео получить превью и подпись к медиа файлу и сформировать ссылку для просмотра поста.

4. Получение медиа-вложений.

К счастью, с недавнего времени, telegram внедрил html превью постов, поэтому можно не сохранять на свой сервер бинарные данные, полученные из клиента, а просто взять ссылку на фото и видео, хранящееся на серверах телеграма.

По названию канала и id поста формируем ссылку формата: t.me/НАЗВАНИЕ_КАНАЛА/ID_ПОСТА?embed=1, например t.me/breakingmash/4193?embed=1

Ну а дальше все просто:

private function telegram_media_parse($posts_data, $source){
        include_once(ROOT_DIR.'/libs/phpQuery.php'); //для парсинга html использую очень быструю  и удобную библиотеку phpQuery

        foreach ($posts_data as &$post_data) {
            if (!empty($post_data['media'])){
                $file_contents = self::loader($post_data['post_url'],'site');//Через curl получаем html код страницы поста.
                $document = phpQuery::newDocumentHTML($file_contents); //Формируем dom-дерево из html строки

                $post_data['post_image'] = preg_replace('/[\s\S]*background-image:[ ]*url\(["\']*([\s\S]*[^"\'])["\']*\)[\s\S]*/u','$1',$document->find($source['rules']['post_img_path'])->eq(0)->attr('style')); //адрес изображения хранится в background-image свойстве.
                $post_data['post_description'] = $document->find($source['rules']['post_text_path'])->eq(0)->text(); //Получаем caption медиафайла. 
            }
            unset($post_data['media']);
        }
        unset($post_data);
        
        return $posts_data;
}

На этом парсинг закончен и можно сохранять посты в базу или вывести на странице.

Надеюсь, что мой первый пост будет кому нибудь полезен. Ссылку на свой агрегатор не оставляю, так как не уверен, разрешено ли это.
Tags:
Hubs:
+7
Comments 28
Comments Comments 28

Articles