Статья Парсим новости в бота с помощью почти забытой технологии. Использование RSS в Python

Начинающие программисты на питоне постоянно натыкаются на уроки о том, как создать бота. И да, боты – это довольно полезная штука, но лишь в том случае, если они еще и выполняют какие-либо полезные функции. С помощью телеграмм-бота можно даже управлять компьютером удаленно. И это не так уж и сложно реализовать. Но, в данной статье мы этого делать не будем. А займемся более прозаическими вещами и все-таки создадим бота, который получает новости. Но в отличии от других мы не будем без ума парсить новостные ленты, чтобы получить заголовки новостей и ссылку. Мы пойдем другим путем. А вот каким, давайте и узнаем в статье.

000.jpg

А начнем мы все же с создания бота, прежде чем будем двигаться дальше. Ведь бот будет каркасом нашего скрипта.


Что потребуется?

Для того, чтобы создать бота, нужно установить библиотеку aiogram. Делается это довольно просто, с помощью команды в терминале:

pip install aiogram

И после того, как она установлена, давайте импортируем сразу же все, что нам пригодиться в дальнейшем. А понадобятся нам из библиотеки Bot, Dispatcher, executor и types. Так же, для оформления отправляемых сообщений в более-менее красивом виде импортируем методы hbold, hlink из модуля markdown.

Python:
from aiogram import Bot, Dispatcher, executor, types
from aiogram.utils.markdown import hbold, hlink

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

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

Python:
bot = Bot(token=token, parse_mode=types.ParseMode.HTML)
dp = Dispatcher(bot)

Токен и id канала я сохранил в отдельном файле, который и импортировал в создаваемый скрипт с помощью:

from config import token, id_channel

Теперь давайте создадим функцию, в которой и будет происходить получение новостей и отправка их в канал или чат. Назовем ее просто start. Или можете назвать как-то иначе. Это не особо принципиально. И укажем в диспетчере, что вызов данной функции будет происходить посредством отправки боту команды /start.

Python:
@dp.message_handler(commands="start")
async def start(message: types.Message):

Само тело функции пока что опущу, тут нужно будет более детально пройтись по всему, что будет в ней твориться. Об этом ниже. А пока запустим бота на исполнение. А вернее создадим для этого функцию, где и поместим команду на запуск.

Python:
if __name__ == "__main__":
    # запускаем бота
    executor.start_polling(dp)

Теперь давайте перейдем к основному телу функции. Как вы уже поняли, нам нужно будет получить новости с какого-либо новостного сайта. И тут есть два путя. Можно парсить, можно не парсить. Я пошел по первому пути. Не сейчас, конечно, а когда только пробовал создать себе новостного бота. Не скажу, что это быстрая задача. Ведь я поставил себе задачу обработать штук пять новостных сайтов. Или даже больше. Уже точно не помню. Но помню, что у меня получились простыни кода, которые пришлось вынести в отдельный модуль и вызывать его в основной функции.

Впрочем, наверное, так и надо делать. Но, просто я помню, сколько было потрачено усилий для того, чтобы просто получить новости. А оказывается, если бы мы не забывали о старых технологиях, о тех технологиях, которые постепенно сходят на нет, но, кое-где еще остаются, то все получилось бы гораздо быстрее и проще. А речь идет об RSS. И тут уже не надо парсить сотни строк кода. Не надо искать тэги, в которых находится текст новости или ссылка на нее. Тут достаточно спарсить RSS, а затем пройтись по полученному xml.

Чем же это сделать? Можно в принципе и руками. Но, оказывается, есть для этого специальная библиотека, которая упрощает это дело до пары тройки строк кода. Суть тут в том, что в RSS содержится вся информация, которая нам нужна для отправки новости в бота. Дата новости, ссылка на новость и краткое содержание при необходимости. Но, когда в чат телеграмма отправляется ссылка, отображается краткое превью. И необходимости в описании нет. Поэтому, все, что нужно забрать, это дата, заголовок и ссылка. Давайте приступим.

Для начала установим библиотеку rss-parser с помощью команды:

pip install rss-parser

И импортируем нужные нам модули в скрипт:

from rss_parser import Parser

А теперь, когда все необходимые приготовления проделаны, давайте перейдем к написанию тела бота. Для начала создадим список, в который будем складывать заголовки. Это нужно для того, чтобы через полчаса не получить ту же самую новость.

habr_title = []

А теперь запускаем бесконечный цикл, в котором и будет происходить получение новостей:

while True:

В первую очередь, чтобы не хранить в памяти слишком много значений списка, проверяем его длину. И если она больше 20, то сбрасываем все его значение.

Python:
if len(habr_title) > 20:
    habr_title = []

Теперь введем переменную, в которой будем хранить ссылку на ленту новостей. У Хабра ссылка, к примеру такая:

rss_url = 'https://habr.com/ru/rss/news/?fl=ru'

Дальше получаем содержимое xml по данной ссылке.

xml = get(rss_url)

Полученное содержимое передаем в парсер, где и происходит основное действо. Тут же можно установить параметр limit, который будет выводить из RSS столько новостей, сколько вы укажете. Это очень похоже на точно такой же параметр lxml, когда парсишь сайт. Ну да ладно, вот код парсера:

parser = Parser(xml=xml.content, limit=3)

Теперь передаем все в переменную:

feed = parser.parse()

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

for item in reversed(feed.feed):

Дальше делаем проверку есть ли заголовок новости в списке или нет. Если есть, новость пропускаем. Если нет, добавляем заголовок в список и отправляем новость в чат или канал.

Python:
if not item.title in habr_title:
    habr_title.append(item.title)
    # await message.answer(f'{hbold(item.publish_date)}\n\n{hlink(item.title, item.link)}\n\n')
    await bot.send_message(id_channel, f'{hbold(item.publish_date)}\n\n{hlink(item.title, item.link)}\n\n')

Как вы понимаете, тут можно пойти двумя путями, про которые я уже упоминал. Выше указан код и для отправки в чат бота, и для отправки в канал, который будет создан вами. В чат бота это закомментированный код отправки, в канал, тот, что раскомментирован.

Давайте пройдемся чуть подробнее по коду. Здесь параметр publish_date, как можно понять из контекста, означает дату публикации. title – заголовок новости, а link – ссылка на нее. Ну и обернут этот код в разметку markdown. Дату публикации я сделал жирной, а вот заголовок и ссылку поместил в специальный тэг, первым параметром у которого служит текст, в данном случае заголовок, а вторым параметром ссылка. Таким образом заголовок в сообщении будет уже содержать в себе ссылку на новость. Что намного эстетичнее, чем то и другое по раздельности.

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

time.sleep(1800)

Вот, собственно, и все. Такой просто и небольшой код, взамен простыней парсинга. Если бы я раньше вспомнил об этой технологии и поискал бы решение, то может быть мои боты работали до сих пор. А того бота я забросил. Уж не помню почему, но, по-моему, код изменился на странице какого-то из порталов. А переписывать парсер мне было просто лень. Тем не менее, это было очень полезно, потому как я, все же получил бесценный опыт.

Python:
# pip install aiogram
# pip install rss-parser
# pip requests

# импортируем библиотеки
import time

from rss_parser import Parser
from requests import get
from aiogram import Bot, Dispatcher, executor, types
from aiogram.utils.markdown import hbold, hlink
from config import token, id_channel

# создаем объект бота, которому передаем токен, а также указываем какого типа будут
# отправляемые сообщения, создаем диспетчера, в которого передаем бота
bot = Bot(token=token, parse_mode=types.ParseMode.HTML)
dp = Dispatcher(bot)


# указываем обработку диспетчером комманды start
# создаем функцию в которой будем отправлять сообщения
# для этого явно указываем на тип сообщений
@dp.message_handler(commands="start")
async def start(message: types.Message):
    habr_title = []

    # запускаем бесконечный цикл, в котором будем проверять наличие новостей
    while True:
        if len(habr_title) >= 20:
            habr_title = []
        rss_url = "https://habr.com/ru/rss/news/?fl=ru"
        xml = get(rss_url)

        parser = Parser(xml=xml.content, limit=3)
        feed = parser.parse()
       
        # пробегаемся по каждой новости в цикле
        for item in reversed(feed.feed):
            # проверяем есть ли заголовок новости в списке
            if not item.title in habr_title:
                habr_title.append(item.title)
                # отправляем сообщение
                # await message.answer(f'{hbold(item.publish_date)}\n\n{hlink(item.title, item.link)}\n\n')
                await bot.send_message(id_channel, f'{hbold(item.publish_date)}\n\n{hlink(item.title, item.link)}\n\n')
        time.sleep(1800)


# запускаем бота
if __name__ == "__main__":
    executor.start_polling(dp)

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

screenshot1.png

Забыл упомянуть, что новости тут грузятся очень быстро. Просто потому, что не нужно тратить время на парсинг кучи страниц, а нужно всего лишь загрузить один xml-файлик.

Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
 

Вложения

  • bot_rss_news.zip
    1,1 КБ · Просмотры: 536

Архонт

Green Team
03.12.2019
20
7
BIT
18
очень хочется захэйтить за "почти забытую технологию", но не буду. на самом деле, многие мои знакомые никогда ее и не знали. А для тех, кто связан с вебом она очень актуальна до сих пор (и Atom как продолжение)
У самого бот долгое время работал на рсс. Позже, когда появилось апи с нужными новостями, переделал на получение с апи.
Для определения новых новостей в своем боте я не храню все заголовки опубликованных новостей, храню только время публикации последней новости. При получении списка новостей, бот выбирает те, что были опубликованы позже, чем сохраненное время
В новой версии бота, там где используется апи, хранится айди новости и публикуются новости с большим айди
 
  • Нравится
Реакции: Johan Van

Johan Van

Green Team
13.06.2020
363
691
BIT
395
очень хочется захэйтить за "почти забытую технологию", но не буду. на самом деле, многие мои знакомые никогда ее и не знали. А для тех, кто связан с вебом она очень актуальна до сих пор (и Atom как продолжение)
У самого бот долгое время работал на рсс. Позже, когда появилось апи с нужными новостями, переделал на получение с апи.
Для определения новых новостей в своем боте я не храню все заголовки опубликованных новостей, храню только время публикации последней новости. При получении списка новостей, бот выбирает те, что были опубликованы позже, чем сохраненное время
В новой версии бота, там где используется апи, хранится айди новости и публикуются новости с большим айди

Позвольте немного пояснить. Вполне возможно, что мое суждение является сугубо субъективным. Я столкнулся с тем, что RSS, как и Atom стали пропадать с сайтов и заменятся соцсетями. А произошло это года два или три назад, когда у меня в браузере скопилась критическая масса ссылок на разные ресурсы и отслеживать все их физически я просто не мог. Тогда я решил поставить себе какой-нибудь RSS-ридер и получать обновления сайтов уже туда. И когда я не нашел RSS на большей их половине, вот примерно тогда начало формироваться мое суждение о том, что данная технология постепенно выходит из употребления. Конечно же, полностью она никуда не уйдет. Но, все больше и больше ее заменяют соцсети. Раньше, помню, можно было даже с помощью Feed Burner сделать себе ленту новостей. А теперь этот ресурс уже толком и не работает. Да и помню времена, когда наличие RSS на сайте было хорошим тоном. А теперь, даже если сайт на CMS и возможность включить RSS есть, она отключается уже намеренно. Ну или ее забывают включать. Тут уж не знаю. На Дзен, так на тот вообще приходилось делать RSS с помощью стороннего ресурса. Так как там вообще не предусмотрено подобное. Может быть поэтому мне на секунду показалось, что данная технология уже почти забыта, а там где она остается, она была и раньше.

А с ботом. Ну, поначалу я хотел тоже сохранять id новости где-нибудь в json, а при проверке его подгружать и просто проверять id на совпадение. Но, потом подумал, что это в данном контексте будет немного избыточно. Тем более, что не на всех новостных сайтах у новости id есть. Я когда первую версию бота для себя делал, то сохранял id и делал проверку. Для некоторых сайтов приходилось формировать его самостоятельно ) Тут же дело не только в Хабре. Хабр - как пример. Я знаю, что там можно и JSON получить. :)
 

Архонт

Green Team
03.12.2019
20
7
BIT
18
если копнуть чуть глубже, то есть куча таких сервисов (tg2rss)
или еще на гитхабе я нашел пхп скрипт для генерации рсс ленты из пабликов вконтакте. и пользуюсь им + Manybot вместо парсинга для пересылки новостей из вк к тг
так что технология не забытая, удобная, но не для интернет маркетинга. новости обязательно должны читать на сайте, чтобы гугл посчитал посетителей и выше ранжировал сайт. и т.д. и т.п.
 
  • Нравится
Реакции: Johan Van

StripedBear

Green Team
24.07.2021
33
23
BIT
11
Спасибо за статью!

Для своего бота я использую feedparser
Тоже достаточно простой модуль
 
  • Нравится
Реакции: Johan Van

Johan Van

Green Team
13.06.2020
363
691
BIT
395
Спасибо за статью!

Для своего бота я использую feedparser
Тоже достаточно простой модуль

Добрый день. Вам спасибо за отзыв. Я просто на него не наткнулся ), но думаю, что и с ним было бы достаточно просто. Суть была в том, чтобы показать, что парсинг новостей с помощью BeautifulSoup не всегда оправдан. Конечно, он позволяет получить бесценный опыт, куда же без этого )), но в плане практичности RSS будет лучше )
 

Архонт

Green Team
03.12.2019
20
7
BIT
18
кстати, я выше писал про Manybot. Это конструктор ботов, которые умеют не только пересылать новости с рсс в личку или в канал, но еще некоторые фичи. Но в этом боте есть задержка (около часа), видимо, из-за большого количества инстансов бота или специальное ограничение. Но основной причиной, почему я решил написать своего, была не задержка, а то, что я хотел, чтобы новость полностью читали в телеге, но бот не может постить длинные тексты и читать в тг их неудобно. от новости до новости нужно долго проматывать. Поэтому я добавил парсинг новости и сначала пощу ее через апи в telegra.ph а потом ссылку уже в телеграм.
Так получаются коротенькие посты с превьюшкой и возможностью просмотра полного текста в инстант вью (в мобильном клиенте)
 
  • Нравится
Реакции: Johan Van

Johan Van

Green Team
13.06.2020
363
691
BIT
395
кстати, я выше писал про Manybot. Это конструктор ботов, которые умеют не только пересылать новости с рсс в личку или в канал, но еще некоторые фичи. Но в этом боте есть задержка (около часа), видимо, из-за большого количества инстансов бота или специальное ограничение. Но основной причиной, почему я решил написать своего, была не задержка, а то, что я хотел, чтобы новость полностью читали в телеге, но бот не может постить длинные тексты и читать в тг их неудобно. от новости до новости нужно долго проматывать. Поэтому я добавил парсинг новости и сначала пощу ее через апи в telegra.ph а потом ссылку уже в телеграм.
Так получаются коротенькие посты с превьюшкой и возможностью просмотра полного текста в инстант вью (в мобильном клиенте)

Кстати да, на telegra.ph читать новости из телеги легче. Это замечательная идея. Так проще чем переходить на сайт с использованием стороннего браузера. Это если в телефоне, конечно.
 

Архонт

Green Team
03.12.2019
20
7
BIT
18
Кстати да, на telegra.ph читать новости из телеги легче. Это замечательная идея. Так проще чем переходить на сайт с использованием стороннего браузера. Это если в телефоне, конечно.
вот мой чутка корявый код для постинга в телеграф

Python:
import re
from bs4 import BeautifulSoup as bs
from telegraph import Telegraph
CHANNEL_PICTURE_URL = "url зображения для превью если в новости нет картинок"
CHANNELS_NAME = "Название канала или источника новости"


def __get_image_width(img_el):
    """Проверяет ширину HTML элемента img и возвращает ее числовое значение в пикселях"""
    try:
        width = img_el['width']
    except KeyError:
        try:
            width = img_el['style'].split('width:')[1]
        except Exception:
            return 0
    return int(width.split('px')[0])


def __get_main_image(soup) -> str:
    """Проверяет ширину всех изображений в новости и возвращает
    ссылку на первое подходящее, либо на заданное по умолчанию,
    если не нашлось подходящего. Нужно для вставки заглавной картинки в пост"""
    images = [i['src']
              for i in soup.find_all('img') if __get_image_width(i) >= 200]
    if images:
        return images[0]
    else:
        return CHANNEL_PICTURE_URL


def make_telegraph_post(title: str, url: str, html: str) -> dict:
    """Формирует телеграф пост из HTML и отправляет его. Возвращает результат отправки"""
    telegraph = Telegraph()
    telegraph.create_account(short_name='short_name')
    
    soup = bs(html, 'html.parser')
    html_spec = str(soup.body)

    # Обработка исходного HTML. Удаление неподходящих для телеграф тегов и замена
    html_spec = re.sub('<\/*body>', '', html_spec)
    html_spec = re.sub('<\/h\d>', '</h1><br />', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('h\d[^>]*>', 'strong>', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*table[^>]*>', '', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*tbody>', '', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('div[^>]*>', 'p>', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*span[^>]*>', '', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*td>', '', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('tr>', 'p>', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*font[^>]*>', '', html_spec, flags=re.IGNORECASE)

    # Вставка заглавного изображения в верх поста и удаление его копии из основного HTML
    main_image = f'<img src="{__get_main_image(soup)}" alt="{title}">'
    html_spec = re.sub(
        f'<\/*img[^>]*{__get_main_image(soup)}[^>]*\/*>', '', html_spec, flags=re.IGNORECASE)




    return telegraph.create_page(
        title,
        author_name=CHANNELS_NAME,
        author_url=url,
        html_content=html_spec
    )
 

Johan Van

Green Team
13.06.2020
363
691
BIT
395
вот мой чутка корявый код для постинга в телеграф

Python:
import re
from bs4 import BeautifulSoup as bs
from telegraph import Telegraph
CHANNEL_PICTURE_URL = "url зображения для превью если в новости нет картинок"
CHANNELS_NAME = "Название канала или источника новости"


def __get_image_width(img_el):
    """Проверяет ширину HTML элемента img и возвращает ее числовое значение в пикселях"""
    try:
        width = img_el['width']
    except KeyError:
        try:
            width = img_el['style'].split('width:')[1]
        except Exception:
            return 0
    return int(width.split('px')[0])


def __get_main_image(soup) -> str:
    """Проверяет ширину всех изображений в новости и возвращает
    ссылку на первое подходящее, либо на заданное по умолчанию,
    если не нашлось подходящего. Нужно для вставки заглавной картинки в пост"""
    images = [i['src']
              for i in soup.find_all('img') if __get_image_width(i) >= 200]
    if images:
        return images[0]
    else:
        return CHANNEL_PICTURE_URL


def make_telegraph_post(title: str, url: str, html: str) -> dict:
    """Формирует телеграф пост из HTML и отправляет его. Возвращает результат отправки"""
    telegraph = Telegraph()
    telegraph.create_account(short_name='short_name')
   
    soup = bs(html, 'html.parser')
    html_spec = str(soup.body)

    # Обработка исходного HTML. Удаление неподходящих для телеграф тегов и замена
    html_spec = re.sub('<\/*body>', '', html_spec)
    html_spec = re.sub('<\/h\d>', '</h1><br />', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('h\d[^>]*>', 'strong>', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*table[^>]*>', '', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*tbody>', '', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('div[^>]*>', 'p>', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*span[^>]*>', '', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*td>', '', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('tr>', 'p>', html_spec, flags=re.IGNORECASE)
    html_spec = re.sub('<\/*font[^>]*>', '', html_spec, flags=re.IGNORECASE)

    # Вставка заглавного изображения в верх поста и удаление его копии из основного HTML
    main_image = f'<img src="{__get_main_image(soup)}" alt="{title}">'
    html_spec = re.sub(
        f'<\/*img[^>]*{__get_main_image(soup)}[^>]*\/*>', '', html_spec, flags=re.IGNORECASE)




    return telegraph.create_page(
        title,
        author_name=CHANNELS_NAME,
        author_url=url,
        html_content=html_spec
    )

Очень интересный код. Если позволите, я себе заберу )) Думаю, что пригодиться. Не обязательно для того, чтобы постить в Телеграф. Тут и по другому его можно использовать. Особенно ту часть, где код чиститься от тэгов.
 
  • Нравится
Реакции: Архонт

Архонт

Green Team
03.12.2019
20
7
BIT
18
Очень интересный код. Если позволите, я себе заберу )) Думаю, что пригодиться. Не обязательно для того, чтобы постить в Телеграф. Тут и по другому его можно использовать. Особенно ту часть, где код чиститься от тэгов.
да, пожалуйста. этот код для моего специфического источника новостей. там у новости табличная верстка, потому что это почтовая рассылка. и поэтому, возможно не любой хтмл он запостит в телеграф. не на всех тестировал
 

StripedBear

Green Team
24.07.2021
33
23
BIT
11
Добрый день. Вам спасибо за отзыв. Я просто на него не наткнулся ), но думаю, что и с ним было бы достаточно просто. Суть была в том, чтобы показать, что парсинг новостей с помощью BeautifulSoup не всегда оправдан. Конечно, он позволяет получить бесценный опыт, куда же без этого )), но в плане практичности RSS будет лучше )
Кстати, в документации наткнулся, что rss-parser также основан на BeautifulSoup, так что далеко от него не ушли :)
 

Johan Van

Green Team
13.06.2020
363
691
BIT
395
Кстати, в документации наткнулся, что rss-parser также основан на BeautifulSoup, так что далеко от него не ушли :)

Ну в принципе, можно xml с помощью bs4 распарсить. В общем-то невелика задача. Но раз есть инструмент, почему нет ))
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!