Vom Parser des Python-Theaterplakats bis zum Telegrammbot. Teil 2



Wir setzen die Geschichte der Entwicklung des Telegramm-Bots für die Ticketsuche fort - HappyTicketsBot, der Anfang ist im ersten Teil zu finden .

Im zweiten Teil werde ich über den Bot selbst sprechen, den Code sowie Ideen teilen, die höchstwahrscheinlich nicht Wirklichkeit werden. Die meisten Funktionen zum Zeitpunkt der Erstellung des Bots waren bereits in einem Skriptformat geschrieben. Daher bestand die Hauptaufgabe darin, eine Benutzerinteraktionsschnittstelle über Telegram-Messenger einzurichten. Es stellte sich nicht so bolzenologisch heraus wie im 1. Teil, daher ist die Aufmerksamkeit viel Code.

Spoiler: HappyTicketsBot ist nicht weggeflogen, um einen fremden Server einzuschalten, er ist lokal und russisch, aber eines Tages (glaube ich) wird er starten =)

UPDATE: Nachdem der Bot in der Theateröffentlichkeit geteilt wurde, schrieben sie darüber in den Medien. Eine Flut von Nutzern stieg stark an. Nach ein paar Tagen des Spiels, "sofort abholen, wie es gefallen ist", flog der Bot zum Server und erfuhr eine Reihe von Verbesserungen. Ich bin zufrieden =)

1. Von vorne anfangen


Da es beim Entwerfen von Telegramm-Bots überhaupt kein Wort gab, musste ich mit grundlegenden Artikeln und Tutorials beginnen, die im Netzwerk sehr zahlreich sind. Ja, übrigens, was ist das Backend zu dieser Zeit, habe ich mir auch schlecht vorgestellt)) Diese Lektionen wurden am informativsten und am besten angewendet. Das Modul, das mit Telegram interagierte, war pyTelegramBotAPI ( github ).

Am längsten dauerte die Entwicklung der Ideologie der Dekorateure, lesen Sie darüber in diesem Artikel . Es gibt zwei Teile und sehr verständlich.

2. Das Skript für die Interaktion des Bots mit dem Benutzer. Grundlegende Suche


Wie bereits im Vorwort und im ersten Teil des Artikels erwähnt , war fast der gesamte Parsing-Code fertig. Es blieb die Methode zum Einstellen der Suchparameter zu ändern. Basierend darauf wurde ein Bot-Verhaltensskript erstellt. Die dem Benutzer zur Verfügung stehenden Befehle sind auf den folgenden Satz beschränkt:

  • / Suchen - Neue Suche starten,
  • / Zurücksetzen - Suchparameter zurücksetzen und einen neuen starten,
  • / LastSearch - gibt Ergebnisse unter Verwendung der Parameter der letzten Abfrage zurück.
  • / addURL Performance-URL im Interesse hinzufügen, um Preissenkungen zu verfolgen,
  • / checkURL - Preise für Aufführungen von Interesse aktualisieren,
  • / showURL - listet alle URLs auf, die der Interessenliste hinzugefügt wurden

Gemäß dem grundlegenden Suchskript / Suchen wechselt der Benutzer von einem Status in einen anderen und gibt nacheinander die für den Filter erforderlichen Daten ein. Nach Eingabe des letzten Parameters - des Präsentationsortes - wird das Poster direkt mithilfe global deklarierter Wörterbücher analysiert. Der Schlüssel ist die Benutzer-ID. Die Werte sind die eingegebenen Suchparameter.

Um sich den Status des Benutzers zu merken, werden sie in der Datenbank gespeichert. Um damit zu arbeiten, werden Vedis-Module (ein Schlüsselwert-Datenbankkonfigurator, lesen Sie die Dokumentation ) und Enum (Arbeiten mit Aufzählungen, Details 1 , 2 ) verwendet.

In einer separaten Konfigurationsdatei Myconfig.py legen wir die Bot-Parameter fest (einschließlich des von Telegram empfangenen eindeutigen Tokens) und listen die Status auf, in denen sich der Benutzer befinden kann. Sie kamen ein wenig heraus.

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" #       

Als Ergebnis erhalten wir eine einfache Kette von Statusübergängen von einem zum anderen.



Zur Speicherung verwenden wir die Vedis-Datenbank. Die Initialisierung des Benutzers, der die Nachricht gesendet hat, erfolgt immer über message.chat.id.

Der Code der Datei dbwoker.py, der die Interaktion mit der Datenbank beschreibt
 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 


Unten finden Sie ein Beispiel für einen Handler, der mit dem Befehl / find aktiviert wird. Wie Sie sehen, gibt es in diesem Beispiel keine Dateneingabe - es gibt nur eine Statusänderung in "S_ENTER_MONTH". Nachdem der Benutzer die Nachricht bei der Eingabe der Nummer gesehen hat, gibt er sie ein und sendet eine Nachricht. Nach Erhalt einer Nachricht mit dem Status S_ENTER_MONTH wird der nächste Schritt eingeleitet. Bei Eingabefehlern ändert sich der Status nicht.

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

Wenn der Bot eine Nachricht von einem Benutzer mit dem Status S_ENTER_MONTH empfängt, wird der folgende Handler gestartet. Ideologisch tritt es auch in anderen Phasen des Basissuchenskripts auf.

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

Neben der Standardsuche können interessante Performances gespeichert werden.

3. Preisänderungen verfolgen


Der Benutzer kann der Liste der Interessen URLs hinzufügen, um eine Benachrichtigung zu erhalten, wenn der Preis fällt. Wir erinnern uns, dass wir in der Basissuche immer noch einen nicht aufgelisteten Status hatten - S_ENTER_URL. In

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

Verwenden Sie zum Speichern der Liste die CSV-Datei. Um damit zu interagieren, benötigen Sie einige Funktionen - Schreiben und Lesen mit Überprüfung der Preisänderungen. Wenn es sich ändert, benachrichtigen Sie den Benutzer.

  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) 

Der Funktionscode für die Preisaktualisierung ist etwas länger
 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) #     .    ... ... 


Infolgedessen kann der Benutzer mit dem Befehl / checkURL ein solches Ergebnis erhalten (jetzt verstehe ich, dass es notwendig wäre, auch den Namen der Aufführung anzuzeigen, aber dies sind Dinge aus der Serie „haben die Hände nicht erreicht“).



Okay, okay. Wir können suchen, wir können verfolgen. Ein paar Freunde haben angefangen, den Bot zu benutzen. Ich wollte herausfinden, wer sie sind und wonach sie suchen. Diese Informationen lassen sich gut in die Protokolle schreiben.

4. Wir schreiben Aktivitäten und Fehler in die Protokolle


Das Protokollierungsmodul hilft uns dabei. Informationen werden erst in der Phase des Abschlusses der Basissuche im Handler aufgezeichnet, in dem der Benutzerstatus von S_ENTER_PLACE an S_START übergeht. Die Fehleraufzeichnung erfolgt wiederum, wenn sie auftritt.

Ich kann nicht viel über die Funktionsweise des Moduls sagen, daher ist es besser, sich an die Informationen außerhalb zu wenden.



Logger Beschreibung
 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") 


Aufgrund einer getrennten Verbindung stürzte der Bot regelmäßig ab, sodass ein Internetverbindungsfehler abgefangen wurde und der Bot nach 10 Sekunden automatisch neu gestartet wurde. Da es jedoch nicht immer gespeichert wurde, habe ich TeamViewer weiter ausgeführt, um es bei Bedarf zu erhöhen.

5. Nicht realisiert


Wir haben einen Bot, der die Funktionalität des Skripts ersetzt, aber es Ihnen ermöglicht, Informationen in einer praktischen Form im Messenger zu erhalten. Er schloss meine Grundbedürfnisse.

Die Demontage mit Modulen und das Schreiben schlanker Handler dauerte an Wochenenden und manchmal abends etwa einen Monat. Am Ende dieses Zeitraums begann das Interesse zu schwinden und die Funktionalität blieb am Ausgangspunkt hängen. Es war nicht möglich, die Prinzipien der Arbeit an Webhook-Ahs zu durchbrechen, und dann wurde das Telegramm blockiert. Davor gab es einen Plan, das Back-End auf einen funktionierenden Server zu ziehen, aber ... vpn wird dafür nicht dort abgelegt =)

Folgendes bleibt in den Plänen, von denen einige an einem trägen Sommer- / Winterabend einmal realisiert werden können und werden:

  • Lasttests mit einem großen Benutzerstrom. Es ist noch nicht klar, ob der Bot stabil funktioniert und die Benutzer nicht verwirrt.
  • Benachrichtigung über das Erscheinen einer neuen Aufführung im Zeitplan des Künstlers. Ich habe viele Lieblings- "weiße Kaninchen", ich kann nicht jeden im Auge behalten (aber ich würde gerne);
  • Benachrichtigung über das Erscheinen beim Verkauf von Tickets einer bestimmten Kategorie. Es gab einen Bekannten, einen Amateur aus der ersten Reihe der Stände, der manuell schwer zu fangen war;
  • Regelmäßige automatische Überprüfung der interessierenden URLs auf Preissenkung durch Timer. Dies geschieht nun auf Befehl, der Timer konnte nicht schnell eingestellt werden, so dass er auf einfache Weise belassen wurde.
  • Erhaltung der Geschichte der Besuche von Aufführungen. Irgendwo in der CSV-Datei, Datum-Name-Aufstellung der Künstler-Ihr Kommentar, um nicht zu verlieren;
  • Suche nach einer bestimmten Kategorie von Tickets. Legen Sie nicht nur den Preis fest, sondern auch den Sektor (Erdgeschoss usw.).
  • Übertragen Sie alles in eine Fähigkeit für Alice. warum nicht
  • Erstellen Sie eine mobile Anwendung mit der gleichen Funktionalität. warum nicht

Es gab einen Anruf im Bolschoi-Theater. Um die Tickets für Nureyev zu bekommen, konnte ich die HTML-Poster jedoch nicht an zwei Abenden abholen, sodass sie auch von der Liste der nicht realisierten gestrichen wurden.

GESAMT


Faulheit war der Motor des Fortschritts und hielt ihn auf. Es kam nicht dazu, den Bot auf einen Server eines Drittanbieters hochzuladen. Dies erfordert jedoch umfassendere Kompetenzen und Kenntnisse im Bereich Web. Das Projekt erwies sich als interessant und ermöglichte es uns, ein wenig besseres Python zu lernen, eine andere Facette davon zu sehen (zusätzlich zum üblichen maschinellen Lernen) und auch viele wundervolle Abende im Theater zu einem Schnäppchenpreis zu präsentieren. Dank ihm schloss er die Aufgaben mit einem Knall ab.

Egal wie ich es versuchte, der Artikel enthielt immer noch viel Code und ein wenig Text. Ich werde gerne das Unverständliche oder Wenige erklären, das in den Kommentaren beschrieben wird =)

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


All Articles