Всем привет! В данной статье я поделюсь своей реализацией бота для telegram, который может переводить статьи из интернета в mp3-файлы. Для этого я буду использовать python 3.6 и соответствующие библиотеки. Итак, приступим.
Для начала надо зарегистрировать своего бота в telegram. На хабре есть статья, в которой это подробно описано. Далее надо установить pip, на всякий случай прикрепляю ссылку на его установку. После установки менеджера пакетов устанавливаем библиотеки, которые помогут осуществить задуманное, выполняем в терминале:
pip install pyttsx3
pip install langdetect
pip install pydub
pip install bs4
pip install telebot
pip install PyTelegramBotAPI
Теперь надо создать три основных файла:
bot.py
parser.py
voice.py
В них будет реализована логика работы приложения. bot.py отвечает непосредственно за работу бота, основные функции всех модулей вызываются в нем. В parser.py реализован парсинг, того что отправлено боту, в voice.py реализован функционал перевода данных после парсинга в аудиоформат. Смысл таков, боту приходит ссылка на статью из интернета, если она удовлетворяет условию(url-адрес ведет на контент, который написан с использованием тега <article>, а не содержащий просто набор текста), текст статьи парсится с помощью bs4 и средствами библиотеки pyttsx3 записывается в аудиофайл, который в дальнейшем конвертируется в mp3 и отправляется пользователю обратно сообщением. Все просто. Понеслась...
bot.py
Я использую декоратор message_handler, чтобы отвечать юзеру на вызов команды /start
import telebot
from voice import get_mp3_file, get_file_name
from parser import get_article_text, get_article_language, get_link
bot = telebot.TeleBot('TOKEN')
@bot.message_handler(commands=['start'])
def forward_message(message):
bot.send_message(message.from_user.id, "Привет, я перевожу статьи в аудиофайлы,"
"пришли мне ссылку на статью, "
"а я сброшу тебе mp3 файл.")
и на сообщения
is_running = False
@bot.message_handler(content_types=['text'])
def forward_message(message):
global is_running # Глобальная переменная для проверки в работе бот или нет
if not is_running:
link = get_link(message.text)
if link: # Проверка, что боту отправили ссылку, а не что-то другое
is_running = True
article_text = get_article_text(link)
article_language = get_article_language(article_text)
if article_language: # Проверка, что статья была распарсена и ее язык получен
bot.send_message(message.from_user.id, "Да, эта статья подходит.")
bot.send_message(message.from_user.id, f"Язык статьи - {article_language[0]}.")
bot.send_message(message.from_user.id, "Отправляю аудиофайл...")
file_name = get_file_name(link)
get_mp3_file(file_name, article_text, article_language[1])
bot.send_audio(message.from_user.id, audio=open(file_name, 'rb'))
else:
bot.send_message(message.from_user.id, "Данная статья не подходит,"
"попробуй прислать другую...")
is_running = False
else:
bot.send_message(message.from_user.id, "Нет, это не ссылка, попробуй "
"прислать ссылку на статью.")
else:
bot.send_message(message.from_user.id, "Я занят предыдущим запросом, "
"подожди немного...")
Глобальная переменная is_running и ее проверка нужна для того, чтобы не вызывать повторно основные функции бота до тех пор, пока они не закончат работу над предыдущим запросом. То есть, боту необходимо отправить аудиофайл или же дать ответ юзеру, что, что-то пошло не так и только потом принимать новый запрос на обработку. В переменной link я проверяю, что боту пришел действительно url-адрес, а в article_language, что статья была удачно распарсена и ее язык получен.
if link: # Проверка, что боту отправлена ссылка, а не что-то другое
is_running = True
article_text = get_article_text(link)
article_language = get_article_language(article_text)
parser.py
import requests
from bs4 import BeautifulSoup
from langdetect import detect
import re
def get_link(message_text):
# Ищем ссылку в отправленном боту сообщении
link_arr = re.findall(r'^https?:\/\/?[\w-]{1,32}'
r'\.[\w-]{1,32}[^\s@]*$', message_text)
if len(link_arr) > 0:
link = link_arr[0]
return link
return False
Чтобы проверить, что пользователь действительно отправил ссылку, я использую регулярное выражение в функции get_link. Если функция отработала и вернула url статьи, я иду дальше и получаю её текст, если вернулся False, бот посылает посылает сообщение: "Нет, это не ссылка, попробуй прислать ссылку на статью."
def get_article_text(link):
try:
# Получаем ответ от сервера, на который ведет ссылка
response = requests.get(link)
except requests.exceptions.ConnectionError:
return False
# Инициализируем парсер
parser = BeautifulSoup(response.content, 'html.parser')
try:
# Парсим статью по тегу <article>
article_text = parser.select_one('article').get_text(separator='. ')
except AttributeError:
return False
return article_text
Здесь я проверяю код ответа, использую для этого соответствующее исключение, если боту был отправлен несуществующий url. При вызове requests.exceptions.ConnectionError возвращается False, который в дальнейшем не пройдет проверку в get_article_language.
Парсинг статьи осуществляется библиотекой BeautifulSoup4 по тегу <article>, с получением всего входящего в тег текста, с разделением через точку с пробелом. Это нужно для того, чтобы разделить слитные части текста, которые могут образоваться при парсинге, в итоге аудиофайл будет прочитан программой с более верной интонацией. get_article_text возвращает строку с содержимым статьи, либо False.
def get_article_language(article_text):
try:
language = detect(article_text) # Определяем язык статьи
except TypeError:
return False
if language == 'en':
return ['EN', ['en_GB']]
if language == 'ru':
return ['RU', ['ru_RU']]
return False
Язык статьи я определяю, используя библиотеку langdetect. Я выбрал в своей реализации всего лишь 2 языка: русский и английский, хотя можно было бы и больше. В функции возвращается либо массив, 0-ой элемент которого идет в отправку сообщения юзеру об языке статьи, а 1-й элемент используется в pyttsx3 для выбора языка чтеца, либо False, тогда бот посылает сообщение: "Данная статья не подходит, попробуй прислать другую…".
voice.py
import pyttsx3
from pydub import AudioSegment
import re
def engine_settings(engine, article_language):
voices = engine.getProperty('voices')
engine.setProperty('rate', 185) # Выставляем скорость чтения голоса
for voice in voices:
if voice.languages == article_language and voice.gender == 'VoiceGenderMale':
return engine.setProperty('voice', voice.id) # Выбираем подходящий голос
def get_mp3_file(file_name, article_text, article_language):
engine = pyttsx3.init()
engine_settings(engine, article_language) # Применение настроек голоса
engine.save_to_file(article_text, file_name) # Сохранение текста статьи в аудиофайл
engine.runAndWait()
convert_file_to_mp3(file_name) # Конвертация в mp3 формат
def convert_file_to_mp3(file_name):
converter = AudioSegment
converter_file = converter.from_file(file_name)
converter_file.export(file_name, format="mp3")
def get_file_name(link):
# Название файла - ссылка на статью
file_name = re.split(r'^https?:\/\/?', link)[1]
for symbols_in_file_name in ['/', '.', '-']:
# Замена символов в названии файла на '_', чтобы сохранить файл в OS
file_name = file_name.replace(symbols_in_file_name, '_')
file_name = file_name+'.mp3' # Сохраняем файл изначально в mp3 формате
return file_name
Для создания аудиофайла я использую библиотеку pyttsx3 как указано было ранее, а для конвертации в mp3 библиотеку pydub. Сначала я задаю настройки в engine_settings, выставляю скорость чтения, в зависимости от языка статьи использую русско-говорящего или англо-говорящего чтеца мужского пола и выбираю его голос.
В get_mp3_file я применяю настройки из engine_settings, сохраняю полученный текст статьи после парсинга в аудиофайл(в get_file_name указываю mp3, но по факту pyttsx3 дает другой формат и в обычных проигрывателях, полученный файл не воспроизводится, поэтому в дальнейшем его надо конвертировать с помощью AudioSegment). После выполнения engine.runAndWait и дальнейшей конвертации в mp3, файл сохранится в текущей директории проекта.
Теперь бот может отправить аудиофайл юзеру. Прикрепляю видео работы бота.
Запускаем
python bot.py
Конечно, у меня получился неидеальный парсинг, причем завязанный на тег <article> и голос в pyttsx3 в некоторых моментах может быть неслушабельным, но задумка по моему мнению неплохая и к тому же работающая. Спасибо за внимание.
vmkazakoff
Coder69 Автор
Согласен, все это есть. Задумка о создании такого бота показалась мне интересной, реализовал и поделился этим, не более того.
Squoworode
AdmAlexus
Астрологи объявили неделю telegram-ботов...
А если серьезно, то уже во второй статье встречаю следующее:
Вопрос… Зачем?
Coder69 Автор
Согласен, если выполняем pip install PyTelegramBotAPI, то нет необходимости… учёл
Tettel
Давайте договоримся на хабре, что ботов будем писать на нормальном фреймворке — aiogram и обойдёмся без глобальных переменных <3
И из придирок: все же вместо двух If'оф, глазам приятнее словари :)
Coder69 Автор
Взял на заметку..