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



Realmente amo la ópera y el ballet, pero en realidad no, doy mucho dinero por las entradas. La visualización diaria del sitio web del teatro con un toque en cada botón era terriblemente agotador, y las entradas repentinas de 170 rublos para los súper trenes fueron desgarradoras.
Para automatizar este negocio, apareció un script que se ejecuta en un póster y recopila información sobre los boletos más baratos para el mes seleccionado. Las solicitudes de la serie "emiten una lista de todas las óperas en marzo en el escenario antiguo y nuevo de hasta 1000 rublos". Un amigo cayó "¿no estás haciendo un bot de Telegram?" Esto no estaba en el plan, pero por qué no. El bot nació, aunque estaba girando en una computadora portátil doméstica.
Entonces Telegram fue bloqueado. La idea de llevar el bot al servidor de trabajo se ha desvanecido, y el interés por recordar la funcionalidad se ha desvanecido. Bajo el corte, hablo sobre el destino de un detective de boletos baratos desde el principio y lo que le sucedió después de un año de uso.

1. El origen de la idea y el enunciado del problema.


En la producción inicial, toda la historia tenía una tarea: crear una lista de actuaciones, filtrada por precio, con el fin de ahorrar tiempo en ver manualmente cada actuación del póster individualmente. El único teatro cuyo cartel fue de interés fue y sigue siendo el Mariinsky. La experiencia personal demostró rápidamente que la "galería" del presupuesto se abre en días aleatorios para actuaciones aleatorias, y se compra lo suficientemente rápido (si el personal está de pie). Para no perderse nada, se necesita un colector automático.
Tipo de póster con botones que tenía que navegar manualmente
imagen

Quería obtener un conjunto limitado de actuaciones de interés para ejecutar el guión. El criterio principal, como ya se mencionó, fue el precio del boleto.
La API del sitio y el sistema de tickets no están disponibles públicamente, por lo que se tomó la decisión (sin más preámbulos) de analizar las páginas HTML, sacando las etiquetas necesarias. Abra el principal, presione F12 y estudie la estructura. Parecía adecuado, por lo que las cosas llegaron rápidamente a la primera implementación.
Está claro que este enfoque no escala a otros sitios con carteles y se desmoronará si deciden cambiar la estructura actual. Si los lectores tienen ideas sobre cómo hacerlos más estables sin una API, escriba los comentarios.

2. La primera implementación. Funcionalidad mínima


Se me ocurrió una implementación con experiencia en Python solo para resolver tareas relacionadas con el aprendizaje automático. Y no hubo una comprensión profunda de html y arquitectura web (y no apareció). Por lo tanto, todo se hizo de acuerdo con el principio "a dónde voy, lo sé, pero ahora vamos a encontrar cómo ir"
Para los primeros borradores, tomó 4 horas de la tarde y una introducción a las solicitudes y los módulos Beautiful Soup 4 (no sin la ayuda de un buen artículo , gracias al autor). Para terminar el boceto, otro día libre. No estoy completamente seguro de que los módulos sean los más óptimos en su segmento, pero han cerrado sus necesidades actuales. Aquí está lo que sucedió en la primera etapa.
La estructura del sitio puede comprender qué información y dónde extraerla. En primer lugar, recopilamos las direcciones de las presentaciones que se encuentran en el póster para el mes seleccionado.
La estructura de la página del póster en el navegador, todo está convenientemente resaltado
imagen

Desde la página html, necesitamos leer las URL puras, luego revisarlas y ver la etiqueta de precio. Así es como se ensambla la lista de enlaces.
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) 

Después de estudiar la estructura de la página con la compra de boletos, además del umbral de precio, decidí darle al usuario la oportunidad de elegir también:

  • tipo de actuación (1 ópera, 2 ballet, 3 conciertos, 4 conferencias)
  • lugar (1 escenario antiguo, 2 escenarios nuevos, sala de conciertos 3, salas de 4 cámaras)

La información se ingresa a través de la consola en un formato numérico; se pueden seleccionar varios números. Dicha variabilidad está dictada por la diferencia en el precio de la ópera y el ballet (la ópera es más barata) y el deseo de mirar sus listas por separado.
El resultado son 4 preguntas y 4 filtros de datos : mes, umbral de precio, tipo, ubicación.

A continuación, revisamos todos los enlaces recibidos. Hacemos get_text y buscamos el precio más bajo, y también extraemos la información relacionada. Debido al hecho de que debe examinar cada URL y convertirla en texto, el tiempo de ejecución del programa no es instantáneo. Sería bueno optimizar, pero no pensé en cómo.
No daré el código en sí, será un poco largo, pero todo es cierto allí de manera adecuada e "intuitiva" con Beautiful Soup 4.
Si el precio es inferior al declarado por el usuario y el tipo de letra corresponde al conjunto, se muestra un mensaje sobre el rendimiento en la consola. Había otra opción para guardar todo esto en .xls, pero no echó raíces. Es más conveniente mirar en la consola e inmediatamente seguir los enlaces que meter un archivo.
imagen

Salieron alrededor de 150 líneas de código. En esta versión, con las funciones mínimas descritas, el script es más vivo que todos los vivos y se ejecuta regularmente con un período de un par de días. Todas las demás modificaciones no se completaron (el punzón se ha apagado) y, por lo tanto, están inactivas o no son más ventajosas en sus funciones.

3. Extensión de la funcionalidad.


En la segunda etapa, decidí hacer un seguimiento de los cambios de precios, almacenando enlaces a actuaciones de interés en un archivo separado (más precisamente, la URL para ellos). En primer lugar, esto es relevante para los ballets: rara vez son muy baratos y no entrarán en el tema del presupuesto general. Pero de 5 mil a 2 veces la caída es significativa, especialmente si el rendimiento es con un elenco estelar, y quería seguirlo.
Para hacer esto, primero debe agregar las URL para el seguimiento, y luego "sacudirlas" periódicamente y comparar el nuevo precio con el anterior.
 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() 

Se lanzó una actualización de precios al comienzo del script principal; no se llevó a cabo por separado. Quizás no sea tan elegante como nos gustaría, pero resuelve su problema. Por lo tanto, la segunda funcionalidad adicional fue monitorear la disminución de los precios de las actuaciones de interés.

Entonces nació el bot Telegram, no tan fácil, rápido, alegre, pero aún así nació. Para no poner todo junto, la historia sobre él (así como sobre ideas no realizadas y un intento de hacer esto con el sitio web del Teatro Bolshoi) estará en la segunda parte del artículo.

RESULTADO: la idea fue un éxito, los usuarios están satisfechos. Tomó un par de fines de semana descubrir cómo interactuar con las páginas html. Afortunadamente, Python es un lenguaje casi para todo y los módulos listos ayudan a clavar un clavo sin pensar en la física del martillo.

Espero que el caso sea útil para los Habrachianos y, tal vez, funcione como un Pendel mágico para finalmente hacer una lista de deseos en mi cabeza durante mucho tiempo.

UPD: Continuando la historia - Parte 2

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


All Articles