Парсинг из телеграм-канала на сайт

Возникла необходимость перекинуть контент с определенного канала на сайт на базе WordPress. Задача разовая, поэтому код особо не оптимизировался. В исходном примере загрузка с Telegram была по 100 записей, поэтому просто установлен лимит по 1 сообщению, чтобы при необходимости можно было вернуть обратно. Смещения, возможно, можно реализовать по другому, до конца не разбирался.

Особенность обхода. Идентификаторы сообщений сохраняются в текстовый файл, чтобы отслеживать дубли. После запуска скрипта список извлекается из файла, сортируется, в переменные заносится максимальное и минимальное значение. Парсинг начинается с последних опубликованных, если фиксируется нахождение в блоке уже опубликованных, переходит к минимальному и продолжает загрузку старых сообщений. То есть пропускается уже опубликованный блок. Канал достаточно большой, изначально планировалось загрузка в несколько этапов. Если будут отслеживаться только новые публикации, код заметно упростится. Далее описание отдельных блоков и в конце код целиком.

api_id = '*****'
api_hash = '*****'
channel_username = ""

Параметры для доступа к API Telegram и название канала, с которого необходимо получить контент

client_wp = Client_w('https://mob25.com/xmlrpc.php', 'логин', 'пароль') 
app = Client("my_session", api_id=api_id, api_hash=api_hash, system_version='4.16.30-vxCUSTOM')

Клиенты для доступа к сайту и Telegram. Во втором случае реализации могут быть различны, однако только в таком варианте удалось избавиться от проблемы, когда после запуска этого скрипта происходит вылет со всех аккаунтов на других устройствах. my_session — произвольное имя, используется для создания текстового файла, в котором сохраняется информация о сессии после первой авторизации. При повторном запуске ручная авторизация уже не требуется.

with app:
    channel = app.get_chat(channel_username)
    offset_id = -1 # начальное значение переменной для смещения
    limit = 1  # Загружаем по одному сообщению
    flag = 1

Смещение показывает, что загрузка будет с последнего опубликованного. flag — переменная, в которой фиксируется пропуск уже опубликованного блока (работа будет описана далее).

with open("list_tg_id.txt", "r", encoding="utf-8") as file_tg:
            list_tg = file_tg.read().split('\n')
        list_tg.sort()      
        id_min, id_max = list_tg[1], list_tg[-1]
        file_path, attachment_url = '', ''

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

        messages = app.get_chat_history(channel.id, offset_id=offset_id, limit=limit)
        if not messages:
            print('Нет сообщения')
            offset_id = messages.id
            continue
        for message in messages:  # оставлено, если потребуется переделать 
            message = message     # на обработку по несколько сообщений  

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

        date_wp = str(message.date)
        date_format = '%Y-%m-%d %H:%M:%S'
        date_published = (datetime.strptime(date_wp, date_format) - timedelta(hours=3))

По задумке посты публикуются с указанием исходного времени. Это позволяет сохранить последовательность новостного канала. Из сообщения извлекается дата, форматируется и заносится в переменную для использования при публикации. Корректировка на 3 часа для московского времени.

        if (str(message.id) in list_tg) and flag:
            offset_id = int(message.id) - (int(id_max) - int(id_min)) + 2
            flag = 0
            continue
        if (not message.caption) or (str(message.id) in list_tg):   # если пусто или уже опубликовано
            offset_id = message.id
            continue
        if 'Текст' in message.caption:  # пропуск сообщений с определенным текстом
            offset_id = message.id
            continue

1 блок. Если текущее сообщение уже было опубликовано и пропуск блока не производился (flag по умолчанию равно 1) производится корректировка смещения, чтобы секция с уже опубликованными сообщениями была пропущена. После этого переменная flag меняется на 0, чтобы в дальнейшем этот блок полностью пропускать и не корректировать смещение повторно.
2 блок. Дополнительная проверка — если сообщение уже опубликовано ранее или пустое, то пропускается, смещение корректируется, чтобы продолжилось движение по публикациям.
3 блок. Пропуск сообщений, которые содержат определенный текст (например, с маркировкой рекламы)

        try:
            title, description = (message.caption).split('\n', 1)
        except:
            offset_id = message.id
            continue

        if len(title) > 250:
            try:
                title, temp = (title).split('. ', 1)
            except:
                offset_id = message.id
                continue
            description = temp + ' ' + description
        if (len(title) < 10) or (len(description) < 10):
            offset_id = message.id   
            continue    

1 блок. На данном канале отдельный заголовок не используется, но основная мысль идет первым предложением, далее перевод строки. Поэтому можно разделить по «\n» и разнести по переменным. Стоит учитывать, что message.caption — это именно сообщения от текущего канала. Цитирования с других каналов в этом случает пропускаются, они находятся в другом блоке message.
2 блок. Если заголовок большой (бывает два предложения). Снова запускается разделение, но уже по символу «.». Отброшенная часть добавляется к телу публикации. Если на этом этапе возникает ошибка, просто пропуск с корректировкой смещения.
3 блок. Еще одна проверка. Если заголовок или тело публикации короткие — пропуск.
Сообщения довольно разношерстные, поэтому проще их пропускать, чем отлавливать ошибки (в данном случае).

        re.sub(r'http\S+', '', description)
        re.sub(r'https\S+', '', description)

Регулярка для вырезания ссылок. По данной схеме можно почистить от разного мусора сообщение.

if message.photo:
            file_path = app.download_media(message.photo)
        if file_path:
            filename = ((file_path).split('_'))[-1]
            data = {
                'name': filename,
                'type': 'image/jpeg',}
            with open(file_path, 'rb') as img:
                data['bits'] = xmlrpc_client.Binary(img.read())
            try:
                response = client_wp.call(media.UploadFile(data))
                attachment_url = response['url']
            except:
                pass
        if file_path and attachment_url:
            description += f'<figure class="wp-block-image size-full">'
            description += f'<img src="{attachment_url}">'
            description += f'</figure>'

Если в публикации есть картинка запускается данный блок, берется просто первая. Название файла сокращается (оно длинное, но в конце есть идентификатор). Далее файл картинки сохраняется в папку, загружается в галерею WordPress, извлекается ссылка и добавляется в тело публикации с нужным оформлением.
В текущем коде добавлена ссылка на источник, тут уже все зависит от потребности.

        file_path = ''
        post = WordPressPost()
        post.terms_names = {'category': ['Название категории'],}
        post.title = title
        post.content = (description)
        post.post_status = 'publish'
        post.comment_status = 'open'
        post.date = date_published
        post.id = client_wp.call(posts.NewPost(post))
        print(message.id)
        with open("list_tg_id.txt", "a", encoding="utf-8") as file_tg:
                file_tg.write(f'{message.id}\n')
        offset_id = message.id  # Обновляем смещение для следующего запроса

Очищаем переменную, чтобы на следующей итерации можно было проверить ее на пустоту и пропускать ошибки.
Далее подготавливаем данные для публикации нового сообщения. После публикации сохраняем идентификатор публикации в файл, чтобы не было дублей. Корректируем смещение для доступа следующей публикации.

Далее исходный код парсера целиком



import time
from pyrogram import Client
import re
import time
from datetime import datetime, timedelta

from wordpress_xmlrpc import Client as Client_w, WordPressPost
from wordpress_xmlrpc.compat import xmlrpc_client
from wordpress_xmlrpc.methods import media, posts

# Вставьте свои данные API
api_id = '*****'
api_hash = '*****'

# Укажите имя канала (например, @channelusername)
channel_username = ""

# Создаем клиент
client_wp = Client_w('https://mob25.com/xmlrpc.php', 'логин', 'пароль') 
app = Client("my_session", api_id=api_id, api_hash=api_hash, system_version='4.16.30-vxCUSTOM')

with app:
    channel = app.get_chat(channel_username)
    offset_id = -1 # начальное значение переменной для смещения
    limit = 1  # Загружаем по одному сообщению
    flag = 1
    while True:
        time.sleep(2) 
        with open("list_tg_id.txt", "r", encoding="utf-8") as file_tg:
            list_tg = file_tg.read().split('\n')
        list_tg.sort()      
        id_min, id_max = list_tg[1], list_tg[-1]
        file_path, attachment_url = '', ''
        messages = app.get_chat_history(channel.id, offset_id=offset_id, limit=limit)
        if not messages:
            print('Нет сообщения')
            offset_id = messages.id
            continue
        for message in messages:  # оставлено, если потребуется переделать 
            message = message     # на обработку по несколько сообщений  
        date_wp = str(message.date)
        date_format = '%Y-%m-%d %H:%M:%S'
        date_published = (datetime.strptime(date_wp, date_format) - timedelta(hours=3))
        if (str(message.id) in list_tg) and flag:
            offset_id = int(message.id) - (int(id_max) - int(id_min)) + 2
            flag = 0
            continue
        if (not message.caption) or (str(message.id) in list_tg):   # если пусто или уже опубликовано
            offset_id = message.id
            continue
        if 'Текст' in message.caption:  # пропуск сообщений с определенным текстом
            offset_id = message.id
            continue
        try:
            title, description = (message.caption).split('\n', 1)
        except:
            offset_id = message.id
            continue

        if len(title) > 250:
            try:
                title, temp = (title).split('. ', 1)
            except:
                offset_id = message.id
                continue
        
            description = temp + ' ' + description
        if (len(title) < 10) or (len(description) < 10):
            offset_id = message.id   
            continue         
        re.sub(r'http\S+', '', description)
        re.sub(r'https\S+', '', description)
        time.sleep(4)
        if message.photo:
            file_path = app.download_media(message.photo)
        if file_path:
            filename = ((file_path).split('_'))[-1]
            data = {
                'name': filename,
                'type': 'image/jpeg',}
            with open(file_path, 'rb') as img:
                data['bits'] = xmlrpc_client.Binary(img.read())
            try:
                response = client_wp.call(media.UploadFile(data))
                attachment_url = response['url']
            except:
                pass
        if file_path and attachment_url:
            description += f'<figure class="wp-block-image size-full">'
            description += f'<img src="{attachment_url}">'
            description += f'</figure>'
        description += '<br><p>Источник: <a href="https://web.telegram.org/k/" target="_blank" rel="noreferrer noopener">Telegram-канал "Название канала"</a></p>'
        file_path = ''
        post = WordPressPost()
        post.terms_names = {'category': ['Название категории'],}
        post.title = title
        post.content = (description)
        post.post_status = 'publish'
        post.comment_status = 'open'
        post.date = date_published
        post.id = client_wp.call(posts.NewPost(post))
        print(message.id)
        with open("list_tg_id.txt", "a", encoding="utf-8") as file_tg:
                file_tg.write(f'{message.id}\n')
        offset_id = message.id  # Обновляем смещение для следующего запроса


Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: