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



Ich liebe Oper und Ballett wirklich, aber nicht wirklich - gib viel Geld für Tickets. Das tägliche Betrachten der Website des Theaters mit einem Druck auf jeden Knopf war furchtbar mühsam, und die plötzlich erscheinenden Tickets von 170 Rubel für Superzüge waren herzzerreißend.
Um dieses Geschäft zu automatisieren, wurde ein Skript angezeigt, das auf einem Poster ausgeführt wird und Informationen zu den günstigsten Tickets für den ausgewählten Monat sammelt. Anfragen aus der Reihe "geben eine Liste aller Opern im März auf der alten und neuen Bühne bis zu 1000 Rubel heraus." Ein Freund ließ fallen: "Machst du keinen Telegramm-Bot?" Das war nicht geplant, aber warum nicht. Der Bot wurde geboren, obwohl er sich auf einem Heim-Laptop drehte.
Dann wurde das Telegramm blockiert. Die Idee, den Bot auf den funktionierenden Server zu schieben, ist geschmolzen, und das Interesse, die Funktionalität in den Sinn zu bringen, ist verblasst. Unter dem Strich spreche ich von Anfang an über das Schicksal eines billigen Ticketdetektivs und darüber, was ihm nach einem Jahr Einsatz passiert ist.

1. Der Ursprung der Idee und die Erklärung des Problems


In der ersten Produktion hatte die ganze Geschichte eine Aufgabe: eine nach Preis gefilterte Liste von Aufführungen zu erstellen, um Zeit beim manuellen Betrachten jeder Aufführung des Posters zu sparen. Das einzige Theater, dessen Plakat von Interesse war, war und ist das Mariinsky. Persönliche Erfahrungen haben schnell gezeigt, dass die Budget- "Galerie" an zufälligen Tagen für zufällige Aufführungen geöffnet ist und schnell genug aufgekauft wird (wenn das Personal steht). Um nichts zu verpassen, wird ein automatischer Sammler benötigt.
Art des Posters mit Schaltflächen, die Sie manuell navigieren mussten
Bild

Ich wollte eine begrenzte Anzahl von Performances von Interesse für die Ausführung des Skripts erhalten. Das Hauptkriterium war, wie bereits erwähnt, der Preis des Tickets.
Die Site-API und das Ticketsystem sind nicht öffentlich verfügbar, daher wurde (ohne weiteres) die Entscheidung getroffen, HTML-Seiten zu analysieren und die erforderlichen Tags herauszuholen. Öffnen Sie die Hauptleitung, drücken Sie F12 und studieren Sie die Struktur. Es sah angemessen aus, so dass die Dinge schnell die erste Implementierung erreichten.
Es ist klar, dass dieser Ansatz nicht auf andere Websites mit Postern skaliert werden kann und zusammenbricht, wenn sie sich entscheiden, die aktuelle Struktur zu ändern. Wenn Leser Ideen haben, wie sie ohne API stabiler werden können, schreiben Sie in die Kommentare.

2. Die erste Implementierung. Minimale Funktionalität


Ich habe eine Implementierung mit Erfahrung mit Python entwickelt, um nur Aufgaben im Zusammenhang mit maschinellem Lernen zu lösen. Und es gab kein tiefes Verständnis für HTML und Webarchitektur (und es erschien nicht). Deshalb wurde alles nach dem Prinzip gemacht, "wohin ich gehe, ich weiß, aber jetzt werden wir finden, wie ich gehen soll".
Für die ersten Entwürfe dauerte es 4 Abendstunden und eine Einführung in die Anfragen und Beautiful Soup 4-Module (nicht ohne die Hilfe eines guten Artikels , danke an den Autor). Um die Skizze fertig zu stellen - ein weiterer freier Tag. Ich bin mir nicht ganz sicher, ob die Module in ihrem Segment am optimalsten sind, aber sie haben ihre aktuellen Anforderungen erfüllt. Hier ist, was in der ersten Phase passiert ist.
Welche Informationen und wo sie abgerufen werden müssen, kann anhand der Struktur der Website verstanden werden. Zunächst sammeln wir die Adressen der Einsendungen, die für den ausgewählten Monat auf dem Poster stehen.
Durch die Struktur der Posterseite im Browser wird alles bequem hervorgehoben
Bild

Auf der HTML-Seite müssen wir die reinen URLs lesen und sie dann durchgehen und das Preisschild sehen. So wird die Linkliste zusammengestellt.
import requests import numpy as np from bs4 import BeautifulSoup def get_text(url): # URL  html r = requests.get(url) text=r.text return text def get_items(text,top_name,class_name): """   html-  "" url-, ..  - .       top_name  class_name   -  <a class="c_theatre2 c_chamber_halls" href="//tickets.mariinsky.ru/ru/performance/WWpGeDRORFUwUkRjME13/"> </a> """ soup = BeautifulSoup(text, "lxml") film_list = soup.find('div', {'class': top_name}) items = film_list.find_all('div', {'class': [class_name]}) dirty_link=[] for item in items: dirty_link.append(str(item.find('a'))) return dirty_link def get_links(dirty_list,start,end): # ""    URL- links=[] for row in dirty_list: if row!='None': i_beg=row.find(start) i_end=row.rfind(end) if i_beg!=-1 & i_end!=-1: links.append(row[i_beg:i_end]) return links # ,    ,      num=int(input('    : ')) #URL  .      ,    =) url ='https://www.mariinsky.ru/ru/playbill/playbill/?year=2019&month='+str(num) #    top_name='container content gr_top' class_name='t_button' start='tickets' end='/">' #  text=get_text(url) dirty_link=get_items(text,top_name,class_name) #   URL-,     links=get_links(dirty_link,start,end) 

Nachdem ich die Struktur der Seite mit dem Kauf von Tickets zusätzlich zur Preisschwelle studiert hatte, beschloss ich, dem Benutzer die Möglichkeit zu geben, auch Folgendes zu wählen:

  • Art der Aufführung (1 Oper, 2 Ballett, 3 Konzert, 4 Vortrag)
  • Veranstaltungsort (1 alte Bühne, 2 neue Bühne, 3 Konzertsaal, 4 Kammersäle)

Informationen werden über die Konsole in einem numerischen Format eingegeben, wobei mehrere Nummern ausgewählt werden können. Diese Variabilität wird durch die unterschiedlichen Preise für Oper und Ballett (Oper ist billiger) und den Wunsch bestimmt, ihre Listen getrennt zu betrachten.
Das Ergebnis sind 4 Fragen und 4 Datenfilter - Monat, Preisschwelle, Typ, Ort.

Als nächstes gehen wir alle erhaltenen Links durch. Wir machen get_text und suchen nach dem niedrigeren Preis dafür und ziehen auch die zugehörigen Informationen heraus. Aufgrund der Tatsache, dass Sie in jede URL schauen und sie in Text konvertieren müssen, ist die Laufzeit des Programms nicht sofort. Es wäre schön zu optimieren, aber ich habe nicht darüber nachgedacht, wie.
Ich werde den Code selbst nicht geben, er wird etwas lang sein, aber mit Beautiful Soup 4 stimmt dort alles angemessen und "intuitiv".
Wenn der Preis niedriger ist als der vom Benutzer angegebene und der Typort dem Satz entspricht, wird in der Konsole eine Meldung über die Leistung angezeigt. Es gab eine andere Option, um all dies in .xls zu speichern, aber es hat keine Wurzeln geschlagen. Es ist bequemer, in die Konsole zu schauen und den Links sofort zu folgen, als in eine Datei zu stöbern.
Bild

Es kamen ungefähr 150 Codezeilen heraus. In dieser Version mit den beschriebenen Mindestfunktionen ist das Skript lebendiger als alle lebenden und wird regelmäßig mit einem Zeitraum von einigen Tagen ausgeführt. Alle anderen Modifikationen wurden entweder nicht abgeschlossen (die Ahle ist abgestorben) und sind daher inaktiv oder in Funktionen nicht vorteilhafter.

3. Erweiterung der Funktionalität


In der zweiten Phase habe ich beschlossen, Preisänderungen zu verfolgen und Links zu interessierenden Leistungen in einer separaten Datei (genauer gesagt der URL zu diesen) zu speichern. Erstens ist dies für Ballette relevant - sie sind sehr selten sehr billig und fallen nicht in die allgemeine Haushaltsfrage. Aber von fünftausend auf das Zweifache ist der Rückgang signifikant, besonders wenn die Leistung mit einer herausragenden Besetzung ist, und ich wollte es verfolgen.
Dazu müssen Sie zuerst die URLs für die Nachverfolgung hinzufügen, sie dann regelmäßig „schütteln“ und den neuen Preis mit dem alten vergleichen.
 def add_new_URL(user_id,perf_url): #user_id ,        - WAITING_FILE = "waiting_list.csv" with open(WAITING_FILE, "a", newline="") as file: curent_url='https://'+perf_url text=get_text(curent_url) #      - , ,,   minP, name,date,typ,place=find_lowest(text) user = [str(user_id), perf_url,str(m)] writer = csv.writer(file) writer.writerow(user) def update_prices(): #        print(' ') WAITING_FILE = "waiting_list.csv" with open(WAITING_FILE, "r", newline="") as file: reader = csv.reader(file) gen=[] for row in reader: gen.append(list(row)) L=len(gen) lowest={} with open(WAITING_FILE, "w", newline="") as fl: writer = csv.writer(fl) for i in range(L): lowest[gen[i][1]]=gen[i][2] #   URL  for k in lowest.keys(): text=get_text('https://'+k) minP, name,date,typ,place=find_lowest(text) if minP==0: #     ,      "" minP=100000 if int(minP)<int(lowest[k]): #   ,    lowest[k]=minP for i in range(L): if gen[i][1]==k: #  -  URL   gen[i][2]=str(minP) print('   '+k+'    '+str(minP)) writer.writerows(gen) add_new_URL('12345','tickets.mariinsky.ru/ru/performance/ZVRGZnRNbmd3VERsNU1R/') update_prices() 

Die Preisaktualisierung wurde zu Beginn des Hauptskripts gestartet und nicht separat durchgeführt. Vielleicht nicht so elegant wie wir möchten, aber es löst das Problem. Die zweite zusätzliche Funktion war die Überwachung des Preisverfalls für interessierende Leistungen.

Dann wurde der Telegrammbot geboren, nicht so einfach, schnell, frech, aber immer noch geboren. Um nicht alles zusammenzufügen, wird die Geschichte über ihn (sowie über nicht realisierte Ideen und den Versuch, dies mit der Website des Bolschoi-Theaters zu tun) im zweiten Teil des Artikels stehen.

ERGEBNIS: Die Idee war ein Erfolg, die Benutzer sind zufrieden. Es dauerte ein paar Wochenenden, um herauszufinden, wie man mit HTML-Seiten interagiert. Glücklicherweise ist Python eine Sprache für fast alles und vorgefertigte Module helfen dabei, einen Nagel zu schlagen, ohne an die Physik des Hammers zu denken.

Ich hoffe, dass der Fall für die Habrachianer nützlich sein wird und vielleicht wie ein magischer Pendel funktioniert, um endlich eine Wunschliste zu erstellen, die lange in meinem Kopf sitzt.

UPD: Fortsetzung der Geschichte - Teil 2

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


All Articles