Do analisador do pôster do teatro Python ao bot do Telegram. Parte 2



Continuamos a história do desenvolvimento do bot do Telegram para busca de ingressos - HappyTicketsBot, o início pode ser encontrado na primeira parte .

No segundo, vou falar sobre o bot em si, compartilhar o código, bem como idéias que provavelmente não se tornarão realidade. A maior parte da funcionalidade no momento em que o bot foi criado já estava gravada em um formato de script, portanto a tarefa principal era estabelecer uma interface de interação do usuário via Telegram-messenger. Acabou não tão boltologicamente como na 1ª parte, então atenção é muito código.

Spoiler: HappyTicketsBot não voou para ligar um servidor estrangeiro, é local e russo, mas um dia será lançado (acredito) =)

ATUALIZAÇÃO: Depois que o bot foi compartilhado entre o público do teatro, eles escreveram sobre isso na mídia. Uma enxurrada de usuários aumentou bastante. Após alguns dias do jogo, "pegue imediatamente, como caiu" o bot voou para o servidor e experimentou uma série de melhorias. Estou satisfeito =)

1. Começando do zero


Como não havia nenhuma palavra no design dos robôs do Telegram, tive que começar com artigos e tutoriais básicos, que são muito numerosos na rede. Sim, a propósito, qual é o back-end da época, eu também imaginava mal))) Esse conjunto de lições se tornou o mais informativo e aplicado. O módulo que interagiu com o Telegram foi pyTelegramBotAPI ( github ).

O mais longo levou o desenvolvimento da ideologia dos decoradores, leia sobre eles neste artigo . Existem duas partes e muito compreensíveis.

2. O script para a interação do bot com o usuário. Pesquisa básica


Como já mencionado no prefácio e na 1ª parte do artigo , quase todo o código de análise estava pronto. Faltava mudar o método de configuração dos parâmetros de pesquisa. Com base nisso, um script de comportamento do bot foi construído. Os comandos disponíveis para o usuário estão limitados ao seguinte conjunto:

  • / Find - inicia uma nova pesquisa,
  • / Reset - redefine os parâmetros de pesquisa e inicia um novo,
  • / LastSearch - retorna resultados usando os parâmetros da última consulta,
  • / addURL adiciona URL de desempenho aos interesses para acompanhar a redução de preço,
  • / checkURL - atualiza os preços para performances de interesse,
  • / showURL - lista todos os URLs adicionados à lista de interesses

De acordo com o script de pesquisa básico / localização, o usuário passa de um status para outro, inserindo seqüencialmente os dados necessários para o filtro. Após inserir o último parâmetro - o local da apresentação - o pôster é analisado diretamente usando dicionários declarados globalmente, em que a chave é o ID do usuário e os valores são os parâmetros de pesquisa inseridos.

Para lembrar o estado do usuário, eles são armazenados no banco de dados. Para trabalhar com isso, são utilizados os módulos Vedis (um configurador de banco de dados de valor-chave, leia a documentação ) e Enum (trabalho com enumerações, detalhes 1 , 2 ).

Em um arquivo de configuração separado Myconfig.py, definimos os parâmetros do bot (incluindo o token exclusivo recebido do Telegram) e listamos os status em que o usuário pode estar. Eles saíram um pouco.

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, obtemos uma cadeia direta de transições de status de um para outro.



Para armazenamento, usamos o banco de dados Vedis. A inicialização do usuário que enviou a mensagem é sempre feita através de message.chat.id.

O código do arquivo dbwoker.py, que descreve a interação com o banco de dados
 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 


Abaixo está um exemplo de um manipulador que é ativado pelo comando / find. Como você pode ver, neste exemplo não há entrada de dados - há apenas uma alteração de status para "S_ENTER_MONTH". Tendo visto a mensagem ao digitar o número, o usuário digita e envia uma mensagem. Após o recebimento de uma mensagem com o status S_ENTER_MONTH, a próxima etapa é iniciada. No caso de erros de entrada, o status não muda.

  #   @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) #   

Se o bot receber uma mensagem de um usuário com o status S_ENTER_MONTH, o manipulador a seguir será iniciado. Ideologicamente, isso também ocorre em outros estágios do script de pesquisa 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) #     

Além da pesquisa padrão, é possível salvar performances interessantes.

3. Acompanhamento das mudanças de preço


O usuário pode adicionar URLs à lista de interesses para receber um alerta quando o preço cair. Lembramos que ainda tínhamos um status não listado na pesquisa básica - S_ENTER_URL. Em

 @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 armazenar a lista, use o arquivo .csv. Para interagir com ele, você precisa de algumas funções - escrever e ler com a verificação das mudanças de preço. Se mudar, notifique o usuário.

  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) 

O código da função de atualização de preços é um pouco mais longo
 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, pelo comando / checkURL, o usuário pode obter esse resultado (agora entendo que seria necessário exibir o nome da performance também, mas essas são as coisas da série "não chegaram às mãos").



Ok, ok. Podemos pesquisar, podemos rastrear. Alguns amigos começaram a usar o bot, eu queria descobrir quem eles são e o que estão procurando. É bom escrever essas informações nos logs.

4. Escrevemos atividades e erros nos logs


O módulo Logging nos ajudará com isso. As informações são registradas apenas no estágio de conclusão da pesquisa básica, no manipulador, no qual o status do usuário passa de S_ENTER_PLACE para S_START. A gravação de erros, por sua vez, ocorre quando eles ocorrem.

Não posso falar muito sobre como o módulo funciona, por isso é melhor recorrer às informações externas .



Descrição do 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") 


Devido a uma conexão desconectada, o bot travava periodicamente; portanto, um erro de conexão com a Internet era detectado e o bot reiniciado automaticamente após 10 segundos. Mas nem sempre ele salvou, por isso mantive o TeamViewer em execução para aumentá-lo, se necessário.

5. Não realizado


Temos um bot que substitui a funcionalidade do script, mas permite que você receba informações de uma forma conveniente dentro do messenger. Ele fechou minhas necessidades básicas.

A desmontagem dos módulos e os manipuladores finos de escrita duravam cerca de um mês no modo de trabalho nos fins de semana e, às vezes, à noite. No final desse período, o interesse começou a desaparecer e a funcionalidade ficou presa no ponto de partida. Não foi possível quebrar os princípios de trabalhar com webhook-ahs e o Telegram foi bloqueado. Antes disso, havia um plano para obter back-end para um servidor em funcionamento, mas ... o vpn não será colocado lá para isso =)

Aqui está o que resta nos planos, alguns dos quais podem e serão realizados uma vez em uma lânguida noite de verão / inverno:

  • teste de carga com um grande fluxo de usuários. Ainda não está claro se o bot funcionará de forma estável e não confundirá os usuários;
  • notificação da aparência de uma nova performance na programação do artista. Eu tenho muitos “coelhos brancos” favoritos, não consigo acompanhar todos (mas gostaria);
  • Notificação da aparência na venda de bilhetes de uma determinada categoria. Havia um conhecido, um amador da primeira fila das barracas, que era difícil de pegar manualmente;
  • verificação automática regular de URLs de interesse para uma redução de preço por temporizador. Agora isso é feito sob comando, o timer não pode ser ajustado rapidamente, portanto foi deixado de uma maneira simples;
  • preservação de sua história de visitas a performances. Em algum lugar do arquivo .csv, digite o nome da data-lista-de-artistas-seu para não perder;
  • procure uma determinada categoria de tickets. Defina não apenas o preço, mas também o setor (térreo, etc.);
  • transferir tudo em uma habilidade para Alice. porque não
  • crie um aplicativo móvel com a mesma funcionalidade. porque não

Houve uma ligação no Teatro Bolshoi. Para pegar os ingressos para Nureyev, mas não consegui pegar os pôsteres em html em duas noites, por isso também foi retirado da lista dos não realizados.

TOTAL


A preguiça era o motor do progresso e o deteve. As coisas não chegaram ao upload do bot para um servidor de terceiros; no entanto, isso requer competências e conhecimentos mais amplos no campo da Web. O projeto acabou sendo interessante e nos permitiu aprender um pouco melhor sobre Python, ver outra faceta dele (além do aprendizado de máquina habitual) e também apresentou muitas noites maravilhosas no teatro por um preço acessível. Graças a ele por isso, ele fechou as tarefas com um estrondo.

Não importa como eu tentei, o artigo ainda tem muito código e pouco texto. Ficarei feliz em explicar o incompreensível ou pouco descrito nos comentários =)

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


All Articles