Del analizador del póster del teatro Python al bot Telegram. Parte 2



Continuamos la historia del desarrollo del bot Telegram para la búsqueda de boletos: HappyTicketsBot, el comienzo se puede encontrar en la primera parte .

En el segundo hablaré sobre el bot en sí, compartiré el código, así como las ideas que probablemente no se harán realidad. La mayor parte de la funcionalidad para cuando se creó el bot ya estaba escrita en un formato de script, por lo que la tarea principal era establecer una interfaz de interacción del usuario a través de Telegram-messenger. Resultó no tan boltológicamente como en la primera parte, por lo que la atención es mucho código.

Spoiler: HappyTicketsBot no se fue volando para encender un servidor extranjero, es local y ruso, pero un día (creo) comenzará =)

ACTUALIZACIÓN: Después de que el bot fue compartido entre el público del teatro, escribieron sobre él en los medios. Una avalancha de usuarios aumentó bruscamente. Después de un par de días del juego, "recógelo de inmediato, cómo cayó", el bot voló al servidor y experimentó una serie de mejoras. Estoy satisfecho =)

1. Comenzando desde cero


Como no había ninguna palabra en el diseño de los bots de Telegram, tuve que comenzar con artículos básicos y tutoriales, que son muy numerosos en la red. Sí, por cierto, cuál es el back-end en ese momento, también me imaginé mal)) Este conjunto de lecciones se convirtió en el más informativo y aplicado. El módulo que interactuó con Telegram fue pyTelegramBotAPI ( github ).

El más largo tomó el desarrollo de la ideología de los decoradores, lea sobre ellos en este artículo . Hay dos partes y muy comprensibles.

2. La secuencia de comandos para la interacción del bot con el usuario. Búsqueda básica


Como ya se mencionó en el prefacio y la primera parte del artículo , casi todo el código de análisis estaba listo. Quedaba por cambiar el método de configuración de los parámetros de búsqueda. En base a esto, se creó un script de comportamiento de bot. Los comandos disponibles para el usuario están limitados al siguiente conjunto:

  • / Buscar : inicia una nueva búsqueda,
  • / Restablecer : restablece los parámetros de búsqueda e inicia uno nuevo,
  • / LastSearch : devuelve resultados utilizando los parámetros de la última consulta,
  • / addURL agrega URL de rendimiento en interés para rastrear la reducción de precios,
  • / checkURL : actualiza los precios de las actuaciones de interés,
  • / showURL : enumera todas las URL agregadas a la lista de intereses

De acuerdo con la secuencia de comandos de búsqueda / búsqueda básica , el usuario se mueve de un estado a otro, ingresando secuencialmente los datos necesarios para el filtro. Después de ingresar el último parámetro, el lugar de presentación, el póster se analiza directamente utilizando diccionarios declarados globalmente, donde la clave es la ID de usuario y los valores son los parámetros de búsqueda ingresados.

Para recordar el estado del usuario, se almacenan en la base de datos. Para trabajar con él, se utilizan módulos Vedis (un configurador de base de datos de valores clave, lea la documentación ) y Enum (trabajo con enumeraciones, detalles 1 , 2 ).

En un archivo de configuración separado Myconfig.py, establecemos los parámetros del bot (incluido el token único recibido de Telegram) y enumeramos los estados en los que puede estar el usuario. Salieron un poco.

from enum import Enum token = "4225555:AAGmfghjuGOI4sdfsdfs5656sdfsdf_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" #       

Como resultado, obtenemos una cadena directa de transiciones de estado de una a otra.



Para el almacenamiento utilizamos la base de datos Vedis. La inicialización del usuario que envió el mensaje siempre se realiza a través de message.chat.id.

El código del archivo dbwoker.py, que describe la interacción con la base de datos.
 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 


A continuación se muestra un ejemplo de un controlador que se activa mediante el comando / find. Como puede ver, en este ejemplo no hay entrada de datos, solo hay un cambio de estado a "S_ENTER_MONTH". Después de ver el mensaje al ingresar el número, el usuario lo ingresa y envía un mensaje. Al recibir un mensaje con el estado S_ENTER_MONTH, se inicia el siguiente paso. En caso de errores de entrada, el estado no cambia.

  #   @bot.message_handler(commands=["find"]) def cmd_find(message): state = dbworker.get_current_state(message.chat.id) """    ,         .     .    ,           """ if state == config.States.S_ENTER_MONTH.value: bot.send_message(message.chat.id, "    .   ") elif state == config.States.S_ENTER_PRICE.value: bot.send_message(message.chat.id, "      ,   ") elif state == config.States.S_ENTER_TYPE.value: bot.send_message(message.chat.id, ", -    ,       :( ...") else: #  ""   "0" -   bot.send_message(message.chat.id, "      ") dbworker.set_state(message.chat.id, config.States.S_ENTER_MONTH.value) #   

Si el bot recibe un mensaje de un usuario con el estado S_ENTER_MONTH, se inicia el siguiente controlador. Ideológicamente, también ocurre en otras etapas del script de búsqueda base.

 @bot.message_handler(func=lambda message: dbworker.get_current_state(message.chat.id) == config.States.S_ENTER_MONTH.value) def user_entering_month(message): if not message.text.isdigit(): bot.send_message(message.chat.id, ",    ") return # 1 num[message.chat.id]=message.text #  if int(num[message.chat.id])>12 or int(num[message.chat.id])<1: bot.send_message(message.chat.id, "     1  12.   ") # 2 return url_list[message.chat.id]=take_url(num[message.chat.id]) #  URL-   if url_list[message.chat.id]==[]: #    bot.send_message(message.chat.id, " ,     .     ") return bot.send_message(message.chat.id, "!      .") dbworker.set_state(message.chat.id, config.States.S_ENTER_PRICE.value) #     

Además de la búsqueda estándar, es posible guardar actuaciones interesantes.

3. Seguimiento de cambios de precios


El usuario puede agregar URL a la lista de intereses para recibir una alerta cuando el precio baje. Recordamos que todavía teníamos un estado no listado en la búsqueda básica: S_ENTER_URL. En

 @bot.message_handler(commands=["addURL"]) def cmd_add_url(message): bot.send_message(message.chat.id, " url,   .  https://") dbworker.set_state(message.chat.id, config.States.S_ENTER_URL.value) #  @bot.message_handler(func=lambda message: dbworker.get_current_state(message.chat.id) == config.States.S_ENTER_URL.value) def user_entering_URL(message): perf_url=message.text user_id=message.chat.id try: add_new_URL(user_id,perf_url) bot.send_message(message.chat.id, '    !') dbworker.set_state(message.chat.id, config.States.S_START.value) #    except: bot.send_message(message.chat.id, 'URL !    !') dbworker.set_state(message.chat.id, config.States.S_ENTER_URL.value) 

Para almacenar la lista, use el archivo .csv. Para interactuar con él, necesita un par de funciones: escribir y leer con la verificación de los cambios de precios. Si cambia, notifique al usuario.

  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) 

El código de la función de actualización de precios es un poco más largo
 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)<int(lowest[k]): #   ,    lowest[k]=minPrice #    for i in range(L): if waitingList[i][1]==k: #  -  URL   waitingList[i][2]=str(minPrice) #  bot.send_message(int(gen[i][0]),'   '+k+'    '+str(minPrice)) writer.writerows(waitingList) #     .    ... ... 


Como resultado, mediante el comando / checkURL el usuario puede obtener dicho resultado (ahora entiendo que también sería necesario mostrar el nombre de la actuación, pero estas son cosas de la serie "no llegaron a las manos").



Vale, vale. Podemos buscar, podemos rastrear. Un par de amigos comenzaron a usar el bot, quería saber quiénes son y qué están buscando. Esta información es buena para escribir en los registros.

4. Escribimos actividad y errores en los registros.


El módulo de registro nos ayudará con esto. La información se registra solo en la etapa de completar la búsqueda básica, en el controlador, en el que el estado del usuario pasa de S_ENTER_PLACE a S_START. El registro de errores, a su vez, ocurre cuando ocurren.

No puedo decir mucho sobre cómo funciona el módulo, por lo que es mejor recurrir a la información externa .



Descripción del registrador
 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") 


Debido a una conexión desconectada, el bot se bloqueó periódicamente, por lo que se detectó un error de conexión a Internet y el bot se reinició automáticamente después de 10 segundos. Pero no siempre se guardaba, así que mantuve TeamViewer ejecutándose para poder subirlo si fuera necesario.

5. No realizado


Tenemos un bot que reemplaza la funcionalidad del script, pero le permite recibir información de forma conveniente dentro del messenger. Cerró mis necesidades básicas.

El desmontaje con módulos y la escritura de manejadores delgados duró aproximadamente un mes en el modo de trabajo los fines de semana y, a veces, por las noches. Al final de este período, el interés comenzó a desvanecerse y la funcionalidad se atascó en el punto de partida. No fue posible romper los principios de trabajar en webhook-ahs, y luego Telegram fue bloqueado. Antes de eso, había un plan para extraer el back-end a un servidor en funcionamiento, pero ... vpn no se colocará allí para esto =)

Esto es lo que queda en los planes, algunos de los cuales pueden y se realizarán una vez en una lánguida noche de verano / invierno:

  • prueba de carga con un gran flujo de usuarios. Todavía no está claro si el bot funcionará de manera estable y no confundirá a los usuarios;
  • notificación de la aparición de una nueva actuación en el calendario del artista. Tengo muchos "conejos blancos" favoritos, no puedo hacer un seguimiento de todos (pero me gustaría);
  • Notificación de la aparición en venta de entradas de una determinada categoría. Había un conocido, un aficionado de la primera fila de los puestos, que era difícil de atrapar manualmente;
  • comprobación automática periódica de las URL de interés para una reducción de precios por temporizador. Ahora esto se hace por orden, el temporizador no se pudo configurar rápidamente, por lo que se dejó de una manera simple;
  • preservación de su historia de visitas a espectáculos. En algún lugar del archivo .csv, date-name-line-up-of-artists-your comment para no perder;
  • buscar una categoría determinada de entradas. Establezca no solo el precio, sino también el sector (planta baja, etc.);
  • transfiere todo a una habilidad para Alice. por que no
  • hacer una aplicación móvil con la misma funcionalidad. por que no

Hubo una llamada en el Teatro Bolshoi. Para atrapar los boletos para Nureyev, pero no pude recoger los carteles html en dos noches, por lo que también se eliminó de la lista de los no realizados.

TOTAL


La pereza era el motor del progreso, y lo detuvo. Las cosas no llegaron a subir el bot a un servidor de terceros; sin embargo, esto requiere competencias y conocimientos más amplios en el campo de la Web. El proyecto resultó ser interesante y nos permitió aprender un poco mejor de Python, ver otra faceta del mismo (además del aprendizaje automático habitual), y también presentó muchas tardes maravillosas en el teatro a un precio de ganga. Gracias a él por esto, cerró las tareas con una explosión.

No importa cómo lo intenté, el artículo todavía tenía mucho código y un pequeño texto. Estaré encantado de explicar lo incomprensible o poco descrito en los comentarios =)

Source: https://habr.com/ru/post/445632/


All Articles