Главная страница

Разработка информационной системы персонализации новостной ленты на базе платформы Telegram


Скачать 2.89 Mb.
НазваниеРазработка информационной системы персонализации новостной ленты на базе платформы Telegram
Дата08.02.2023
Размер2.89 Mb.
Формат файлаpdf
Имя файлаZinovyeva_rpz_1.pdf
ТипДокументы
#926704
страница4 из 4
1   2   3   4
2.3.2. Интеграция модуля анализа схожести новостей
Обученная модель позволяет вычислять числовой вектор для каждой текстовой новости. Полученный числовой вектор уже может быть использован для сравнения новостей. Каждая поступившая новость сравнивается с уже имеющимися посредством вычисления косинусной разницы между векторами, если косинусная разница меньше чем определенное значение, то новости считаются одинаковыми.
Программный код модуля анализа схожести новостей представлен на листинге 6.
Листинг 6 – Код модуля анализа схожести новостей.
from string import punctuation
from typing import List
import nltk
from gensim.models.doc2vec import Doc2Vec
from nltk.corpus import stopwords
from pymorphy2 import MorphAnalyzer nltk.download('stopwords') russian_stopwords = stopwords.words('russian') punctuation += '«»'

45 morph = MorphAnalyzer() model = Doc2Vec.load('model')
def preprocess_text(text: str) -> List[str]:
'''
Preprocesses a text: strips punctuation, converts text to lowercase, removes stopwords, tokenizes and lemmatizes text.
Parameters: text: str
Returns: str
''' tokens
=
[morph.parse(word.strip(punctuation))[0].normal_form
for word in text.lower().split()]
return [token for token in tokens if token not in russian_stopwords]
def are_news_same(article_1: str, article_2: str) -> bool: success
= model.similarity_unseen_docs(preprocess_text(article_1), preprocess_text(article_2))
if success >= 0.5:
return True
return False
2.4. Сценарии использования и интерфейс
2.4.1. Подписка на источники
Разрабатываемая система предназначена для получения новостей в одной ленте из различных источников, фильтрации новостей и уменьшения количества уведомлений за счет обнаружения повторных новостей. Можно выделить два основных действия, совершаемых пользователем, это подписка на источники новостей (каналы) и назначение фильтров.

46
Для подписки на новостной источник пользователю необходимо направить в бот ссылку на канал. Ссылки на каналы в Telegram имеют следующий вид: https://t.me/channelname.
В таком случае сценарий имеет следующий вид:
- команда «Добавить канал»;
- направление ссылки на канал;
- обратная связь после внесения канала в список подписок.
Интерфейс реализации описанного выше сценария представлена на рисунке 4.

47
Рисунок 4 – Интерфейс добавления каналов
2.4.2. Редактирование фильтров
Для назначения фильтров пользователю необходимо направить в бот ключевые слова, которые необходимо либо исключить из новостной ленты, либо наоборот включить в ленту. Данные слова можно обозначить знаками «+» или

48
«-». Тогда пользователю будет предложено внести ключевые слова через запятую отметив каждое слово соответствующим знаком, например:
+осенний фестиваль, +акции, -реклама
Интерфейс реализации описанного выше сценария представлена на рисунке 5.
Рисунок 5 – Интерфейс редактирования фильтров

49
2.4.3. Редактирование каналов
После подписки на список каналов у пользователя возникает необходимость просмотреть список каналов, удалить каналы. Каналы выдаются пользователю в виде списка, а возможность удаления канала реализуется через команду напротив канала.
Интерфейс реализации описанного выше сценария представлена на
Рисунке 6.
Рисунок 6 – Интерфейс редактирования каналов

50
3. Практическая часть
3.1. Формирование фокус-группы
Для оценки результатов работы системы, выявления ошибок, учета пользовательского опыта и определения путей развития системы принято решение провести пробное использование системы группой не менее чем из 20 пользователей в течении одной рабочей недели. По результатам пробного использования у пользователей проведен опрос.
Для проведения опроса использовалась облачная площадка Google Forms.
Google Forms позволяет создавать произвольные опросные листы и предоставляет инструментарий по аналитике результатов опроса.
Для достижения поставленных перед опросом задач были определены следующие вопросы.
1) Понравилось ли вам использование данного бота?
2) Считаете ли вы его полезным для себя?
3) На сколько хорошо бот справлялся с поставленными задачами?
4) Легко ли вы разобрались в интерфейсе бота?
5) Встречали ли вы ошибки в работе бота?
3.2. Опрос фокус-группы
Результаты опроса представлены на рисунках 7 – 11.

51
Рисунок 7 – Результаты опроса. Вопрос 1
Большая часть участников опроса в фокус-группе оценили впечатление от использования бота на высший бал, при этом средняя оценка составила 8,77 баллов.
Рисунок 8 – Результаты опроса. Вопрос 2
Полезность бота была оценена участниками в среднем на 7,81. Оценка полезности оказалась ниже, чем впечатления от использования. Для работы над этим параметром могут быть разработаны предварительно настроенные

52 подборки каналов и фильтры для того, чтобы пользователь не тратил время и силы на настройку, а сразу приступал к использованию.
Рисунок 9 –
Результаты опроса. Вопрос 3
Участники дали высокую оценку качеству работы бота, она составила в среднем 8,86. Высокие показатели качества работы, помимо качества разработки могут быть связаны так же с понятными для пользованью задачами, поставленными перед ботом.
Рисунок 10 –
Результаты опроса. Вопрос 4

53
Оценка удобства интерфейса бота составила в среднем 7,54.
Рисунок 11 – Результаты опроса. Вопрос 5
Достаточно высокое число участников столкнулась с ошибками при работе бота, а именно 6 из 22. Основной ошибкой была ошибка в интерфейсе бота, когда не пользователь не мог вызвать основное меню из некоторых сценарных веток.
Данная ошибка была устранена.
3.3. Примеры работы модуля анализа схожести новостей
Пример работы модуля анализа новостей представлен на рисунке 12. В начале поста выделен заголовок новости на основании четырех схожих новостей из следующих каналов.
1. RT на русском.
2. The Bill.
3. Mash.

54 4. IZ.RU.
Далее перечисляются все ссылки на новости из вышеперечисленных каналов. Их модуль схожести новостей определил, как одинаковые. На рисунках
13-16 приведены оригинальные тексты новостей из источников, упомянутых на рисунке 12.
Рисунок 12 – Объединенная из 4 источников новость

55
Рисунок 13 – Новость в источнике 1

56
Рисунок 14 – Новость в сточнике 2

57
Рисунок 15 – Новость в источнике 3
Рисунок 16 – Новость в источнике 4

58
ЗАКЛЮЧЕНИЕ
В рамках выпускной квалификационной работы была выполнена разработка системы персонализации новостной ленты на базе платформы
Telegram.
В ходе выполнения работы проведен анализ предметной области, обзор современных технологий реализации информационных систем, обзор возможностей платформы Telegram и обзор методов анализа схожести текстов.
Выбрано технологическое и решение для реализации системы.
Разработаны архитектура системы с учетом архитектуры Telegram, структура базы данных, сценарии работы системы, интерфейс системы и реализованы алгоритмы серверной части системы.
Разработана и обучена нейронная сеть для обнаружения одинаковых новостей и произведена интеграция нейронной сети в информационную систему.
Для оценки результатов работы системы проведено пробное использование системы среди фокус-группы, с последующем проведением опроса. По результатам опроса установлено, что система интересна и полезна пользователем.
Таким образом, работа выполнена в полном объёме. Система удовлетворяет всем поставленным требованиям технического задания и пригодна для дальнейшей эксплуатации.

59
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1. Сенаторов А. А. Telegram. Как запустить канал, привлечь подписчиков и заработать на контенте. – М.: Альпина Паблишер, 2018. –156 с.
2. Статья BBC [Электронный ресурс] – Режим доступа: URL: https://www.bbc.com/russian/vert-fut-52774764 (Дата обращения 20.03.2022)
3. Официальная документация Telegram MTProto [Электронный ресурс]
– Режим доступа: URL: https://core.telegram.org/mtproto (Дата обращения
25.04.2022)
4. Официальная документация Telegram API. [Электронный ресурс] –
Режим доступа: URL: https://core.telegram.org/methods (Дата обращения
01.05.2022)
5. Осипов Д. Л. Технологии проектирования баз данных. – М.: ДМК,
2019. – 497c.
6. Кристофер Д. Маннинг, Прабхакар Рагхаван, Хинрич Шютце.
Введение в информационный поиск. – Пер. с англ. – М.: Вильяме, 2011. – 428c.
7. Статья Medium [Электронный ресурс] – Режим доступа: URL: https://medium.com/@bigdataschool/как-работает-word2vec-нейросети-для-nlp-
1609812f80e5 (Дата обращения 15.05.2022)
8. Лен Басс. Архитектура программного обеспечения на практике, 2-е издание. – Пер. с англ. – СПб.: Питер, 2006.
– 574 с.
9. С. В. Назаров. Архитектура и проектирование программных систем. –
М.: ИНФРА-М, 2013. – 413c.
10. Майкл Нильсен. Neural Networks and Deep Learning. – NY.: Chapter,
2019. – 653c.
11. Ян Гудфеллоу, Иошуа Бенджио, Аарон Курвилль. Глубокое обучение.
2-е издание. – Пер. с англ. – М.: ДМК, 2018. – 652 с.

60
ПРИЛОЖЕНИЕ А. Графическая часть
В графическую часть выпускной квалификационной работы входят следующие материалы.
1) Архитектурная схема разрабатываемой системы.
2) Иллюстрация работы системы 1. Объединение новостей из четырех источников в один пост.
3) Иллюстрация работы системы 2. Первый источник.
4) Иллюстрация работы системы 3. Второй источник.
5) Иллюстрация работы системы 4. Третий источник.

61
ПРИЛОЖЕНИЕ Б. Исходный код telegram-bot
server
main.py
from aiogram.utils import executor from create_bot import dp, ids, bot from db import user as us from antiflood import ThrottlingMiddleware async def on_startup(_): global ids ids+=(us.get_all_users_ids()) print(‘Бот запущен’) async def on_shutdown(_): await dp.storage.close() await dp.storage.wait_closed() from handlers import add_channels, manage_channels, ui, cancel, registration cancel.register_handlers_cancel(dp) add_channels.register_handlers_add_channels(dp) manage_channels.register_handlers_manage_channels(dp) registration.register_handlers_reg(dp) ui.register_handlers_ui(dp) dp.middleware.setup(ThrottlingMiddleware()) executor.start_polling(dp, skip_updates=True, on_startup=on_startup, on_shutdown=on_shutdown)
add_channels.py
from aiogram import types, Dispatcher, filters from attr import validate from create_bot import dp, bot, ids, r from db import user as us from antiflood import rate_limit from keyboards import user_kb from aiogram.dispatcher import FSMContext

62 from aiogram.dispatcher.filters.state import State,
StatesGroup from aiogram.utils.markdown import escape_md class AddChannel(StatesGroup): subject = State() filter_one = State() class MultiAddChannel(StatesGroup): channels = State() confirm = State() async def add(msg):
# Добавление канала if type(msg)
== types.callback_query.CallbackQuery: await msg.answer() msg = msg.message await AddChannel.subject.set() await msg.answer(“Для добавления канала отправьте пригласительную ссылку в формате https://t.me/123”, reply_markup=user_kb.kb_cancel_inl) async def process_subject(msg: types.Message, state:
FSMContext):
# Ввод имени канала await state.update_data(subject=msg.text) await AddChannel.next() await msg.answer(‘Введите фильтры через запятую в формате +осенний фестиваль, +акции, -реклама. \
Вы ни при каких обстоятельствах не будете получать новостей со словами после «-«, \ а если укажете что-либо после «+», то будете получать только те новости где есть эти слова. \
Можно использовать только «-« или только «+». Если хотите убрать фильтрацию, то нажмите на кнопку ниже', reply_markup=user_kb.remove_filters) async def process_subject_finish(msg: types.Message, state: FSMContext):
# Добавление канала в БД subject = (await state.get_data())[‘subject’] uid = msg.from_user.id if type(msg)
== types.callback_query.CallbackQuery:

63 await msg.answer() msg = msg.message fltrs = ‘’ else: for I in msg.text.split(‘,’):
I = i.strip() if I == ‘’: await msg.reply(f'Не ставьте запятые по краям сообщения и по несколько запятых подряд, введите фильтры еще раз или нажмите «Отмена»', reply_markup=user_kb.kb_cancel_inl) return if len(i) <= 2: await msg.reply(f'Фильтр состоит минимум из знака и двух символов, введите фильтры еще раз или нажмите «Отмена»', reply_markup=user_kb.kb_cancel_inl) return if i[0]!=’+’ and i[0]!=’-‘: await msg.reply(f'Вы забыли указать + или – перед фразой «{i}», введите фильтры еще раз или нажмите «Отмена»', reply_markup=user_kb.kb_cancel_inl) return fltrs = msg.text if us.if_has_link(uid, subject): await msg.answer(«Уже имеется такой канал») await state.finish() else: us.add_news(user_id=int(uid), channel_link=subject, filters=fltrs) await msg.answer(“Канал внесён в список”, reply_markup=user_kb.menu) r.set(str(uid) + “ “ + subject, ‘добавить’) await state.finish() async def add_multi(msg: types.Message):
# Добавление нескольких каналов await MultiAddChannel.channels.set() await message.reply(«Вводите названия каналов или ссылки через запятую») async def check_text(msg: types.Message, state=FSMContext): await state.update_data(text=msg.text)

64 await msg.reply(‘Всё правильно?’, reply_markup=user_kb.check_text) await MultiAddChannel.next() async def multi_finish(cb: types.CallbackQuery, state:
FSMContext):
# Непосредственно добавление if cb.data == 'ok': text:str = (await state.get_data())[‘text’] channels = [] for I in text.split(‘,’):
I = i.strip() if us.if_has_link(cb.from_user.id, i): await cb.message.answer(«Уже имеется канал «+i) else: us.add_news(user_id=cb.from_user.id, channel_link=i) r.set(str(cb.from_user.id) + “ “ + I,
‘добавить’) await cb.answer(«Новые каналы были внесены в список. Отредактировать фильтры вы можете через главное меню», show_alert=True) else: await cb.answer(«Добавление отменено», user_kb.menu) await state.finish() def register_handlers_add_channels(dp: Dispatcher): dp.register_message_handler(add, commands=[‘add_channel’], state=’*’) dp.register_callback_query_handler(add, text=”add_channel”) dp.register_message_handler(process_subject, state=AddChannel.subject) dp.register_message_handler(check_text, state=MultiAddChannel.channels) dp.register_callback_query_handler(multi_finish, state=MultiAddChannel.confirm) dp.register_message_handler(process_subject_finish, state=AddChannel.filter_one)

65 dp.register_callback_query_handler(process_subject_finish
, text=’remove_filters’, state=AddChannel.filter_one)
sender.py
import telebot # Синхронный аналог aiogram
from telebot.types
import
InputMediaPhoto,
InputMediaVideo
from settings import token #
�”обавляем токен из файла настроек bot = telebot.TeleBot(token)
def send(id, url, text, media=None): text = text+’\n
�сточники:’+url
if media[‘video’] == [] and media[‘photo’] == []: msg = bot.send_message(id, text)
elif media[‘video’] == [] and len(media[‘photo’])
== 1: msg
= bot.send_photo(id, str(media[‘photo’][0]), caption=text)
elif media[‘photo’] == [] and len(media[‘video’])
== 1: msg
= bot.send_video(id, str(media[‘video’][0]), caption=text)
else: medias = []
for I in media[‘photo’]: medias.append(InputMediaPhoto(i))
for I in media[‘video’]: medias.append(InputMediaVideo(i)) bot.send_media_group(id, medias) msg = bot.send_message(id, text)
return msg.message_id, text
def edit(phrase, id, mid): bot.edit_message_text(phrase, id, mid)
return mid, phrase

66
ПРИЛОЖЕНИЕ В. Исходный код telegram-bot
server
import asyncio
from telethon import TelegramClient, events
from telethon.tl.functions.channels
import
JoinChannelRequest
from telethon.tl.custom.message import Message
from telethon.tl.types import MessageMediaDocument,
MessageMediaPhoto
from telethon.events import StopPropagation
import os
import sys
from settings import api_id, api_hash, host_redis, port_redis, charset, decode_responses, queue_db_redis
import redis
import compare_news as ai
from db import user as us
from sender import send, edit
import datetime client = TelegramClient('grab', api_id, api_hash) channels_to_join = us.get_all_channels() # Получаем каналы для парсинга lst = [] # Сюда тоже скоро поступят каналы r = redis.StrictRedis( # Подключаем бд host=host_redis, port=port_redis, charset=charset, decode_responses=decode_responses, db=queue_db_redis
) async def news_processing(msg: Message, text, name, media): lm_text = ai.preprocess_text(text) #Леммы из текста date = msg.date #
�"ата поста users = us.get_users_of_channel(name) # Получаем всех подписчиков

67 interest = []
for i in users:
if us.check_text(lm_text, us.get_user_filters(i)): interest.append(us.User.get(us.User.id==i)) # Отбираем кому подходит по фильтрам
if not interest: # Если никому, то новость не нужна
return channels_to_search = [] channels = {}
for i in interest: #Каждый пользователь
for ii in us.get_channels_of_user(i.id): #
Каждый канал Пользователя
if ii.channel_name != name: channels[i.id]=(ii.channel_name)
#
�-аписываем под него, если не источник новости
if ii.channel_name
not
in channels_to_search: channels_to_search.append(ii.channel_name) #
� добавляем в общий список если не повторялся to_check
= us.get_news_for_check(channels_to_search, channels.keys()) # Получаем список пользователей и новостей к проверке repeat = []
if to_check == {}: us.register(send(users[0], f"\n{(date+datetime.timedelta(hours=3)).strftime('%H:%M')
} {name} - https://t.me/{msg.chat.username}/{msg.id}", text, media),text, lm_text, media, name, users[0], date)
for i in to_check.keys():
for ii in to_check[i]:
if
(ii.id
in repeat
or ai.are_news_same(text, ii.text)): repeat.append(ii.id) us.register(edit(f"{ii.edit_text}\n{(date+datetime.timede lta(hours=3)).strftime('%H:%M')}
{name}
- https://t.me/{msg.chat.username}/{msg.id}", i,
(us.switch_np(ii.sent))[i]),text, lm_text, media, name, i, date)# Ссылка на источник

68
else: us.register(send(i, f"\n{(date+datetime.timedelta(hours=3)).strftime('%H:%M')
} {name} - https://t.me/{msg.chat.username}/{msg.id}", text, media),text, lm_text, media, name, i, date)
# await client.forward_messages(entity=channel_to_send_news, messages=event.message)
@client.on(events.Album(lst)) async def my_event_handler(event):
"""Обнаруживает сообщения с несколькими вложениями""" media = {'video':[], 'photo':[]} name = await event.get_chat().title text = ''
for i in event: i:Message
if i.text != '': text = i.text
if
False:# type(i.media)
==
MessageMediaDocument: media['video'].append(i.media.document.access_hash)
if False:# type(i.media) == MessageMediaPhoto: media['photo'].append(i.media.photo.access_hash) await news_processing(event[0].message, text, name, media) #Отправка в фильтр await event.delete()
@client.on(events.NewMessage(lst)) async def my_event_handler(event): msg:Message = event.message
if msg.grouped_id != None: # Пропускает уже обработанные
return name = (await event.get_chat()).title text = msg.text media = {'video':[], 'photo':[]}
if
False:# type(msg.media)
==
MessageMediaDocument:

69 media['video'].append(msg.media.document.access_hash)
if False:# type(msg.media) == MessageMediaPhoto: media['photo'].append(msg.media.photo.access_hash) await news_processing(msg, text, name, media) #
Отправка в фильтр async def join_channel(channel_link):
"""Присоединение к каналам пользователей""" channel = await client.get_entity(channel_link) res = await client(JoinChannelRequest(channel)) title = res.chats[0].title us.add_news_to_user(channel_link, title) lst.append(title) async def load_data(): await asyncio.sleep(5)
for i in r.keys():
if r[i] == 'добавить': r[i] = 'добавлено'
print("restart now") os.execv(sys.executable,
['python']
+ sys.argv)
return await load_data() client.start() ev = asyncio.get_event_loop()
for link in channels_to_join: asyncio.run_coroutine_threadsafe(join_channel(link), ev) asyncio.run_coroutine_threadsafe(load_data(), ev) client.run_until_disconnected()
1   2   3   4


написать администратору сайта