Как написать парсер на python для Телеграмма

Недавно ко мне обратился друг с просьбой написать бота, импортирующего новости из RSS-канала на сайте в Telegram-канал. Огромнейшим плюсом данного способа оповещения являются push-уведомления, которые приходят каждому подписанному пользователю на его устройство. Уже давно хотелось заняться чем-то подобным. Недолго думая, в качестве образца я выбрал канал Хабра telegram.me/habr_ru. В качестве языка программирования был выбран Python.

В итоге, мне надо было решить следующие проблемы:

  1. Парсинг RSS.
  2. Одним из условий был отложенный постинг сообщений (если после того, как новость была выложена, в течение n часов её скрыли/удалили/переименовали, то она не должна быть опубликована, вместо нее отправляется оповещение о корректной новости)
  3. Постинг сообщений в телеграм.
  4. Сокращение целевой ссылки с помощью сервиса bit.ly

От себя добавил еще:

  1. Ведение логов с помощью библиотеки (logging).
  2. Обработка конфига (configparser).

1. Отложенный постинг сообщений

Telegram-бот + парсер на Python


Для решения данной проблемы было принято решение использовать SQLite базу данных. Для работы с БД использовалась библиотека SQLalchemy.

Структура до банального проста — всего одна таблица. Код объекта представлен ниже:

Для хранения текстовой информации и ссылок использется base64, форматом хранения даты-времени был выбран Unix Timestamp.

Обработка данных сессии осуществляется отдельным классом.

При обнаружении новости, она добавляется в базу. Сразу же задается время публикации.

Для обнаружения новостей готовых к публикации используется метод get_post_withwithout_message_id . Фактически, мы выбираем из базы все посты, у которых message_id=0 и дата публикации меньше текущего времени.

Для проверки на новизну отправляем запрос базе данных на факт содержания ссылки на новость (метод find_link ).

Метод update служит для обновления данных, после публикации новости в канале.

2. Парсинг RSS

Стоит признаться, что писать свой RSS парсер совсем не хотелось, поэтому в бой вступила библиотека feedparser.

Код до смешного прост. При вызове метода refresh с помощью генератора формируется список объектов класса News из последних 30 размещенных постов в rss ленте.

3. Сокращение ссылок

Как упоминалось выше, в качестве сервиса был выбран bit.ly. API не вызвает лишних вопросов.

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

4. Управляющий класс

При инциализации с помощью библиотеки configparser считываем наш конфиг-файл и настраиваем логгирование.

Чтобы детектировать новости, используем метод detect . Получаем последние 30 опубликованных постов, поочередно проверяем наличие ссылки в базе данных.

Перед публикацией, необходимо проверить наличие постов, выгруженных из базы данных в rss-канале. В этом нам помогут множества. И после этого уже публикуем новость с помощью библиотеки telegram. Её функционал довольно широк и ориентирован на написание ботов. После публикации необходимо обновить message_id и chat_id .

В итоге получаем:

image

Стоит отметить то, что если переписать класс rss, то так же можно будет импортировать новости из других источников (VK, facebook и т.д.).

Исходники можно найти на Github: https://github.com/Vispiano/rss2telegram

Источник: messagu.ru

Парсер Хабра

Продолжаем историю о разработке Telegram-бота для поиска билетов — HappyTicketsBot, начало можно почитать в первой части.
Во второй расскажу о самом боте, поделюсь кодом, а также идеями, которым скорее всего не суждено стать реальностью. Большая часть функционала к моменту создания бота уже была написана в формате скрипта, поэтому основной задачей стояло наладить интерфейс взаимодействия с пользователем через Telegram-messenger. Получилось не так болтологически, как в 1й части, так что attention — много кода.

Спойлер: HappyTicketsBot так и не улетел крутиться на иностранный сервер, он локальный и русский, но однажды запуск (верю) состоится =)

1. Старт с нуля

Так как опыта в проектировании Telegram-ботов не было от слова совсем, то пришлось начать с базовых статей и tutorials, которых в сети очень много. Да, кстати, что такое back-end на тот момент я тоже плохо представляла)) Самым содержательным и прикладным стал вот этот набор уроков . Модуль, на котором велось взаимодействие с Telegram — pyTelegramBotAPI (github).
Дольше всего заняло освоение идеологии декораторов, про них читала в этой статье. Там в двух частях и очень понятно.

2. Сценарий взаимодействия бота с пользователем. Базовый поиск

  • /Find — начать новый поиск,
  • /Reset — сбросить параметры поиска и начать новый,
  • /LastSearch — выдает результаты, используя параметры последнего запроса,
  • /addURL добавить URL спектакля в интересы для отслеживания снижения цены,
  • /checkURL — обновить цены на интересующие спектакли,
  • /showURL — вывести все URL, добавленные в список интересов

По базовому поисковому сценарию /find пользователь переходит от одного статуса к другому, последовательно вводя необходимые для фильтра данные. После ввода последнего параметра — места представления — происходит непосредственно парсинг афиши с использованием глобально объявленных словарей, где ключ-ID пользователя, значения — введенные параметры поиска.
Для того, чтобы запоминать состояние пользователя, они сохраняются в базе. Для работы с ней используются модули Vedis (конфигуратор баз данных типа ключ-значение, почитать документацию) и Enum (работа с перечислениями, подробнее 1, 2).

В отдельном файле-конфигурации Myconfig.py задаем параметры бота (в том числе полученный от Telegram уникальный token) и перечисляем статусы, в которых может находиться пользователь. Их вышло немного.

from enum import Enum token = «422358437:AAGmOrnuGOI4sdfsdfs5656sdfsdf_c» #токен бота db_file = «Mydatabase.vdb» class States(Enum): «»» в БД Vedis хранимые значения всегда строки, поэтому и тут будем использовать тоже строки (str) «»» S_START = «0» # Начало нового диалога S_ENTER_MONTH = «1» S_ENTER_PRICE = «2» S_ENTER_TYPE = «3» S_ENTER_PLACE = «4» S_ENTER_URL=»5″ #этот статус не входит в базовый поиск

В итоге получаем незамысловатую цепочку перехода статусов из одного в другой.

Для хранения используем БД Vedis. Инициализация пользователя, приславшего сообщение, всегда осуществляется через message.chat.id.

Код файла dbwoker.py, в котором описано взаимодействие с базой

from vedis import Vedis import Myconfig as config # Запрашиваем из базы статус пользователя def get_current_state(user_id): with Vedis(config.db_file) as db: try: return db[user_id] except KeyError: #Если такого ключа/пользователя в базе не оказалось return config.States.S_START.value #Значение по умолчанию-начало диалога # Сохраняем текущий статус пользователя в базу def set_state(user_id, value): with Vedis(config.db_file) as db: try: db[user_id] = value return True except: print(‘Проблемка с юзером!’) # тут желательно как-то обработать ситуацию return False

Ниже пример хендлера, который активизируется по команде /find. Как можно заметить, в этом примере нет никакого ввода данных — есть только смена статуса на «S_ENTER_MONTH». Увидев сообщение о вводе номера, пользователь его вводит и отправляет сообщение. При получении сообщения со статусом S_ENTER_MONTH, инициируется следующий этап. В случае ошибок ввода статус не меняется.

Если бот получает сообщение от пользователя со статусом S_ENTER_MONTH, то запускается приведенный ниже хендлер. Идеалогически также происходит на других этапах сценария базового поиска.

Помимо стандартного поиска, есть возможность сохранять интересные спектакли.

3. Отслеживание изменения цен

Пользователь может добавить в список интересов URL, чтобы получить оповещение, когда цена снизится. Мы помним, что у нас остался незадейстованный в базовом поиске статус — S_ENTER_URL. В

Для хранения списка используем .csv файл. Для взаимодействия с ним нужна пара функций — записи и чтения с проверкой изменения цены. Если она изменяется-оповещаем пользователя.

def add_new_URL(user_id,perf_url): WAITING_FILE = «waiting_list.csv» with open(WAITING_FILE, «a», newline=»») as file: curent_url=’https://’+perf_url text=get_text(curent_url) #функция описана в 1й части статьи minPrice, name,date,typ,place=find_lowest(text) user = [str(user_id), perf_url,str(minPrice)] writer = csv.writer(file) writer.writerow(user)
Код функции обновления цены чуть более длинный
def update_prices(bot): WAITING_FILE = «waiting_list.csv» with open(WAITING_FILE, «r», newline=»») as file: reader = csv.reader(file) waitingList=[] for row in reader: waitingList.append(list(row)) L=len(waitingList) lowest=<> with open(WAITING_FILE, «w», newline=»») as fl: writer = csv.writer(fl) for i in range(L): lowest[waitingList[i][1]]=waitingList[i][2] #добавляем по ключу URL цену for k in lowest.keys(): text=get_text(‘https://’+k) minPrice, name,date,typ,place=find_lowest(text) #об этом говорилось в 1й части статьи if minPrice==0: #если билетов нет minPrice=100000 if int(minPrice)

В результате, по команде /checkURL пользователь может получить такой результат (сейчас понимаю, что надо бы название спектакля тоже выводить, но это вещи из серии «руки не дошли»).

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

4. Пишем активность и ошибки в логи

В этом нам поможет модуль Logging. Запись информации происходит только на этапе завершения базового поиска, в хендлере, в котором статус пользователя переходит из S_ENTER_PLACE в S_START. Запись ошибок, в свою очередь, происходит при их возникновении.
Много сказать о том, как модуль работает, я не смогу, поэтому лучше обратиться к информации вовне.

Описание логгера

def save_logs(str): loggerInfo.info(str) #добавляем строчку в лог logging.basicConfig(format = u’%(levelname)-8s [%(asctime)s] %(message)s’, level = logging.ERROR, filename = u’loggerErrors.log’) global loggerInfo loggerInfo = logging.getLogger(__name__) loggerInfo.setLevel(logging.INFO) handler = logging.FileHandler(‘loggerUsers.log’) handler.setLevel(logging.INFO) formatter = logging.Formatter(‘%(asctime)s — %(name)s — %(levelname)s — %(message)s’) handler.setFormatter(formatter) loggerInfo.addHandler(handler) log = logging.getLogger(«ex»)

Из-за разрыва соединения бот периодически падал, поэтому ошибка интернет соединения отлавливалась и бот перезапускался автоматом через 10 секунд. Но это не всегда спасало, поэтому держала запущенным TeamViewer, чтобы при необходимости поднять.

5. Нереализованное

  • нагрузочное тестирование с большим потоком пользователей. Пока непонятно, будет ли бот работать стабильно и не путать пользователей;
  • оповещение о появлении в расписании артиста нового спектакля. Любимых «белых кроликов» у меня много, за всеми не уследить (а хотелось бы);
  • оповещении о появлении в продаже билетов определенной категории. Был один знакомый, любитель первого ряда партера, который словить сложно вручную;
  • регулярная автоматическая проверка интересующих URL-ов на предмет снижения цены по таймеру. Сейчас это делается в конце базового поиска, таймер не удалось наладить быстро, так что оставила по-простому;
  • сохранение своей истории посещений спектаклей. Куда-нибудь в файлик .csv, дату-название-состав исполнителей-свой комментарий, чтобы не растерять;
  • поиск заданной категории билетов. Задавать не только цену, но и сектор (партер-бенуар и т.д);
  • перенести всё в навык для Алисы. why not?
  • сделать мобильное приложение с тем же функционалом. why not?

ИТОГ

Лень оказалась двигателем прогресса и она же его остановила. До выгрузки бота на сторонний сервер дело не дошло, все-таки это требует более широких компетенций и знаний в области Web. Проект выдался интересным и позволил освоить чуть лучше Python, увидеть еще одну его грань (помимо привычного Machine learning-а), а также подарил много чудесных вечеров в театре по бросовой цене. Спасибо ему за это, поставленные задачи он закрывал на ура.

Как ни старалась, в статье всё равно получилось много кода и мало текста. Буду рада пояснить непонятное или мало описанное в комментариях =)

Источник: habrparser.blogspot.com

От парсера афиши театра на Python до Telegram-бота. Часть 2

От парсера афиши театра на Python до Telegram-бота. Часть 2

2019-03-27 в 21:43, admin , рубрики: enum, html, python, telebot, vedis, Занимательные задачки, парсинг, Программирование

От парсера афиши театра на Python до Telegram-бота. Часть 2 - 1

Продолжаем историю о разработке Telegram-бота для поиска билетов — HappyTicketsBot, начало можно почитать в первой части.
Во второй расскажу о самом боте, поделюсь кодом, а также идеями, которым скорее всего не суждено стать реальностью. Большая часть функционала к моменту создания бота уже была написана в формате скрипта, поэтому основной задачей стояло наладить интерфейс взаимодействия с пользователем через Telegram-messenger. Получилось не так болтологически, как в 1й части, так что attention — много кода.

Спойлер: HappyTicketsBot так и не улетел крутиться на иностранный сервер, он локальный и русский, но однажды запуск (верю) состоится =)

1. Старт с нуля

Так как опыта в проектировании Telegram-ботов не было от слова совсем, то пришлось начать с базовых статей и tutorials, которых в сети очень много. Да, кстати, что такое back-end на тот момент я тоже плохо представляла)) Самым содержательным и прикладным стал вот этот набор уроков . Модуль, на котором велось взаимодействие с Telegram — pyTelegramBotAPI (github).
Дольше всего заняло освоение идеологии декораторов, про них читала в этой статье. Там в двух частях и очень понятно.

2. Сценарий взаимодействия бота с пользователем. Базовый поиск

Как уже упомянуто в предисловии и 1-й части статьи, почти весь код парсинга был готов. Оставалось изменить способ задания параметров поиска. Исходя из этого и был построен сценарий поведения бота. Команды, доступные для пользователя, ограничиваются следующим набором:

  • /Find — начать новый поиск,
  • /Reset — сбросить параметры поиска и начать новый,
  • /LastSearch — выдает результаты, используя параметры последнего запроса,
  • /addURL добавить URL спектакля в интересы для отслеживания снижения цены,
  • /checkURL — обновить цены на интересующие спектакли,
  • /showURL — вывести все URL, добавленные в список интересов

По базовому поисковому сценарию /find пользователь переходит от одного статуса к другому, последовательно вводя необходимые для фильтра данные. После ввода последнего параметра — места представления — происходит непосредственно парсинг афиши с использованием глобально объявленных словарей, где ключ-ID пользователя, значения — введенные параметры поиска.
Для того, чтобы запоминать состояние пользователя, они сохраняются в базе. Для работы с ней используются модули Vedis (конфигуратор баз данных типа ключ-значение, почитать документацию) и Enum (работа с перечислениями, подробнее 1, 2).

В отдельном файле-конфигурации Myconfig.py задаем параметры бота (в том числе полученный от Telegram уникальный token) и перечисляем статусы, в которых может находиться пользователь. Их вышло немного.

from enum import Enum token = «422358437:AAGmOrnuGOI4sdfsdfs5656sdfsdf_c» #токен бота db_file = «Mydatabase.vdb» class States(Enum): «»» в БД Vedis хранимые значения всегда строки, поэтому и тут будем использовать тоже строки (str) «»» S_START = «0» # Начало нового диалога S_ENTER_MONTH = «1» S_ENTER_PRICE = «2» S_ENTER_TYPE = «3» S_ENTER_PLACE = «4» S_ENTER_URL=»5″ #этот статус не входит в базовый поиск

В итоге получаем незамысловатую цепочку перехода статусов из одного в другой.

От парсера афиши театра на Python до Telegram-бота. Часть 2 - 2

Для хранения используем БД Vedis. Инициализация пользователя, приславшего сообщение, всегда осуществляется через message.chat.id.

Код файла dbwoker.py, в котором описано взаимодействие с базой

from vedis import Vedis import Myconfig as config # Запрашиваем из базы статус пользователя def get_current_state(user_id): with Vedis(config.db_file) as db: try: return db[user_id] except KeyError: #Если такого ключа/пользователя в базе не оказалось return config.States.S_START.value #Значение по умолчанию-начало диалога # Сохраняем текущий статус пользователя в базу def set_state(user_id, value): with Vedis(config.db_file) as db: try: db[user_id] = value return True except: print(‘Проблемка с юзером!’) # тут желательно как-то обработать ситуацию return False

Ниже пример хендлера, который активизируется по команде /find.

Как можно заметить, в этом примере нет никакого ввода данных — есть только смена статуса на «S_ENTER_MONTH». Увидев сообщение о вводе номера, пользователь его вводит и отправляет сообщение. При получении сообщения со статусом S_ENTER_MONTH, инициируется следующий этап. В случае ошибок ввода статус не меняется.

Если бот получает сообщение от пользователя со статусом S_ENTER_MONTH, то запускается приведенный ниже хендлер. Идеалогически также происходит на других этапах сценария базового поиска.

Помимо стандартного поиска, есть возможность сохранять интересные спектакли.

3. Отслеживание изменения цен

Пользователь может добавить в список интересов URL, чтобы получить оповещение, когда цена снизится. Мы помним, что у нас остался незадейстованный в базовом поиске статус — S_ENTER_URL. В

Для хранения списка используем .csv файл. Для взаимодействия с ним нужна пара функций — записи и чтения с проверкой изменения цены. Если она изменяется-оповещаем пользователя.

def add_new_URL(user_id,perf_url): WAITING_FILE = «waiting_list.csv» with open(WAITING_FILE, «a», newline=»») as file: curent_url=’https://’+perf_url text=get_text(curent_url) #функция описана в 1й части статьи minPrice, name,date,typ,place=find_lowest(text) user = [str(user_id), perf_url,str(minPrice)] writer = csv.writer(file) writer.writerow(user)
Код функции обновления цены чуть более длинный
def update_prices(bot): WAITING_FILE = «waiting_list.csv» with open(WAITING_FILE, «r», newline=»») as file: reader = csv.reader(file) waitingList=[] for row in reader: waitingList.append(list(row)) L=len(waitingList) lowest=<> with open(WAITING_FILE, «w», newline=»») as fl: writer = csv.writer(fl) for i in range(L): lowest[waitingList[i][1]]=waitingList[i][2] #добавляем по ключу URL цену for k in lowest.keys(): text=get_text(‘https://’+k) minPrice, name,date,typ,place=find_lowest(text) #об этом говорилось в 1й части статьи if minPrice==0: #если билетов нет minPrice=100000 if int(minPrice)

От парсера афиши театра на Python до Telegram-бота. Часть 2 - 3

В результате, по команде /checkURL пользователь может получить такой результат (сейчас понимаю, что надо бы название спектакля тоже выводить, но это вещи из серии «руки не дошли»).

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

4. Пишем активность и ошибки в логи

В этом нам поможет модуль Logging. Запись информации происходит только на этапе завершения базового поиска, в хендлере, в котором статус пользователя переходит из S_ENTER_PLACE в S_START. Запись ошибок, в свою очередь, происходит при их возникновении.
Много сказать о том, как модуль работает, я не смогу, поэтому лучше обратиться к информации вовне.

От парсера афиши театра на Python до Telegram-бота. Часть 2 - 4

Описание логгера

def save_logs(str): loggerInfo.info(str) #добавляем строчку в лог logging.basicConfig(format = u’%(levelname)-8s [%(asctime)s] %(message)s’, level = logging.ERROR, filename = u’loggerErrors.log’) global loggerInfo loggerInfo = logging.getLogger(__name__) loggerInfo.setLevel(logging.INFO) handler = logging.FileHandler(‘loggerUsers.log’) handler.setLevel(logging.INFO) formatter = logging.Formatter(‘%(asctime)s — %(name)s — %(levelname)s — %(message)s’) handler.setFormatter(formatter) loggerInfo.addHandler(handler) log = logging.getLogger(«ex»)

Из-за разрыва соединения бот периодически падал, поэтому ошибка интернет соединения отлавливалась и бот перезапускался автоматом через 10 секунд. Но это не всегда спасало, поэтому держала запущенным TeamViewer, чтобы при необходимости поднять.

5. Нереализованное

У нас получился бот, заменяющий функционал скрипта, но позволяющий получать информацию в удобной форме внутри мессенджера. Основные мои потребности он закрыл.
Разборки с модулями и написание стройных хендлеров длились около месяца в режиме работы по выходным и иногда по вечерам. В конце этого периода интерес уже стал угасать и функционал застрял на начальной точке. Пробиться через принципы работы на webhook-ах с наскока не удалось, а потом и Telegram заблокировали. До этого был план запулить крутиться back-end на рабочий сервер, но… vpn ради этого там ставить не будут=)
Вот что осталось в планах, некоторые из которых может и реализуются однажды томным летним/зимним вечером:

  • нагрузочное тестирование с большим потоком пользователей. Пока непонятно, будет ли бот работать стабильно и не путать пользователей;
  • оповещение о появлении в расписании артиста нового спектакля. Любимых «белых кроликов» у меня много, за всеми не уследить (а хотелось бы);
  • оповещении о появлении в продаже билетов определенной категории. Был один знакомый, любитель первого ряда партера, который словить сложно вручную;
  • регулярная автоматическая проверка интересующих URL-ов на предмет снижения цены по таймеру. Сейчас это делается в конце базового поиска, таймер не удалось наладить быстро, так что оставила по-простому;
  • сохранение своей истории посещений спектаклей. Куда-нибудь в файлик .csv, дату-название-состав исполнителей-свой комментарий, чтобы не растерять;
  • поиск заданной категории билетов. Задавать не только цену, но и сектор (партер-бенуар и т.д);
  • перенести всё в навык для Алисы. why not?
  • сделать мобильное приложение с тем же функционалом. why not?

Был заход на Большой театр. Чтобы словить билеты на «Нуреева», но не удалось за два вечера расковырять html афиши, так что тоже отложено в перечень нереализованного.

ИТОГ

Лень оказалась двигателем прогресса и она же его остановила. До выгрузки бота на сторонний сервер дело не дошло, все-таки это требует более широких компетенций и знаний в области Web. Проект выдался интересным и позволил освоить чуть лучше Python, увидеть еще одну его грань (помимо привычного Machine learning-а), а также подарил много чудесных вечеров в театре по бросовой цене. Спасибо ему за это, поставленные задачи он закрывал на ура.

Как ни старалась, в статье всё равно получилось много кода и мало текста. Буду рада пояснить непонятное или мало описанное в комментариях =)

Источник: www.pvsm.ru

Рейтинг
( Пока оценок нет )
Загрузка ...