#!/usr/bin/env python #-*- coding: iso8859-15 -*- ########################################################## # WEB v1.0 # ########################################################## # Autor: Juan Miguel Taboada Godoy # # Fecha: Szczecin, 15 de febrero de 2007 # # Descripción: Adquisitor de datos de servidor WEB # # Versión: 2007021500 # # # # Codigo fuente bajo licencia GNU/GPL # # Centrologic (Computational Logistic Center) # # http://www.centrologic.com - info@centrologic.com # ########################################################## # Librerías que voy a usar {{{1 from urllib import urlopen from lib.DATE import timeepoch from lib.HERENCIA import BASE,DEFAULTCONFIG # }}}1 # ### CONFIG ### ###################################### ## Acceso a Telemando mediante WEB: configuracion por defecto class CONFIG(DEFAULTCONFIG): # Constructor {{{1 ## Constructor ## \param self - def __init__(self): # Cargo las acciones del padre DEFAULTCONFIG.__init__(self,"FILE_WEB") # }}}1 # Config Telemando {{{1 # download_descarga {{{2 ## Actualizar cada n-segundos ## \param self - ## \param arg Segundos entre acutalizaciones def download_tempo(self,arg=None): if (arg!=None): self._download_tempo=arg return self._download_tempo; # }}}2 # dirtemp {{{2 ## Directorio donde almacenar temporalmente los registros descargados ## \param self - ## \param arg Directorio local def dirtemp(self,arg=None): if (arg!=None): self._dirtemp=arg return self._dirtemp # }}}2 # bloqueo {{{2 ## Fichero que controla el bloqueo ## \param self - ## \param arg Nombre del fichero def bloqueo(self,arg=None): if (arg!=None): self._bloqueo=arg return self._bloqueo # }}}2 # }}}1 # Checker {{{1 ## Comprueba que el conjunto minimo de datos requeridos por la clase han sido declarados por el usuario: \n ## - download_tempo() ## - debug() ## - dirtemp() ## - bloqueo() ## \param self - def check(self): self._check("download_tempo") self._check("debug") self._check("dirtemp") self._check("bloqueo") self.check_default() # }}}1 # ### TELEMANDO ### ################################### ## Acceso a Telemando mediante WEB: Telemando class TELEMANDO(BASE): # Constructor {{{1 ## Constructor ## \param self - ## \param id Identificador del Telemando (ALFANUMERICO) ## \param registrador Registrador a usar def __init__(self,id,registrador): # Inicializo los filtros self.__filtros=[] # Inicializo el viaje temporal self._viaje_temporal=None self.__viaje_temporal_newdatetime=None # Inicializo el rango self._rango_temporal=None # Inicialmente no hacer debuging de las funciones de particion y seleccion self._debug_partselect=False # Inicio el lector de ficheros self.__REGISTRADOR=registrador # Cargo las acciones del padre BASE.__init__(self,id,"FILE_WEB") # }}}1 # Guarda la config {{{1 ## Guarda la configuracion del Telemando ## \param self - ## \param config Copnfiguración por defecto obtenida de la clase CONFIG ## \param url URL de la web con los datos def config(self,config,url): # Configuración por defecto self.default_config(config) # Cargo los datos en la config self.url(url) # Chequeo la config self.check() # }}}1 # Guarda la URL {{{1 ## URL con la web ## \param self - ## \param arg URL def url(self,arg=None): if (arg!=None): self._url=arg return self._url # }}}1 # Viaje Temporal {{{1 ## Viaje temporal a aplicar a los datos ## \param self - ## \param arg Filtro que contiene el valor def viaje_temporal(self,arg=None): if (arg!=None): self._viaje_temporal=arg return self._viaje_temporal # }}}1 # Debug de las funciones de particionado y seleccion {{{1 ## Indica si hay que hacer debuging a las funciones de particionado y seleccion ## \param self - ## \param arg True/False def debug_partselect(self,arg=None): if (arg!=None): self._debug_partselect=arg return self._debug_partselect # }}}1 # Devuelve el listado de registradores {{{1 ## Devuelve el listado de registradores ## \param self - def registradores(self): return self.__REGISTRADOR.list() # }}}1 # Checker {{{1 ## Comprueba que el conjunto minimo de datos requeridos por la clase han sido declarados por el usuario: \n ## - url() ## \param self - def check(self): self._check("url") self.check_default() # }}}1 # Devuelve si una linea tiene contenido válido o no {{{1 ## \internal ## Devuelve si una linea tiene contenido válido o no ## \param self - ## \param texto Texto a revisar def contenido_valido(self,texto): # Quito espacios HTML if (texto==' '): return False # Compruebo la linea for i in texto: if ((i=='.') # . or (i==',') # , or (ord(i)>=48 and ord(i)<=57) # 0-9 or (ord(i)>=65 and ord(i)<=90) # A-Z or (ord(i)>=97 and ord(i)<=122) # a-z ): return True return False # }}}1 # Convierte texto HTML a texto ASCII {{{1 ## \internal ## Convierte texto HTML a texto ASCII ## \param self - ## \param texto Texto a convertir def filtrar(self,texto): texto=texto.replace(" "," ") texto=texto.replace("ñ","n") # ñ texto=texto.replace("á","a") # á texto=texto.replace("é","e") # é texto=texto.replace("í","i") # í texto=texto.replace("ó","o") # ó texto=texto.replace("ú","u") # ú texto=texto.replace("Ñ","N") # Ñ texto=texto.replace("Á","A") # Á texto=texto.replace("É","E") # É texto=texto.replace("Í","I") # Í texto=texto.replace("Ó","O") # Ó texto=texto.replace("Ú","U") # Ú texto=texto.replace("°","") # º texto=texto.replace("©","") # (C) texto=texto.replace("\xf1","n") # ñ texto=texto.replace("\xe1","a") # á texto=texto.replace("\xe9","e") # é return texto # }}}1 # Lee la web de la página indicada y particiona el contenido {{{1 ## Particiona el contenido de la página indicada ## \param self - def leeweb(self): # Recojo la URL url=self.valor("url") # Abro la URL try: FILE=urlopen(url) except Exception,e: self.debug("Error al conectar a la web: %s" % (e)) raise ConnectionError,"Error al conectar a la web: %s" % (e) # Cargo el contenido try: contenido_web=FILE.readlines() except Exception,e: self.debug("Error al leer el contenido de la web: %s" % (e)) raise ConnectionError,"Error al leer el contenido de la web: %s" % (e) # Cierro el fichero try: FILE.close() except Exception,e: self.debug("Error al desconectar de la web:" % (e)) # Inicializo datos contenido=[] elemento="" tag="" leyendo_tag=False num_fila=0 comentario=False script=False linea_old=None # Para cada línea for linea in contenido_web: # Incremento el numero de lineas num_fila=num_fila+1 # Indico que no hay nada que saltar saltar=0 # Para cada caracter de la linea for i in range(0,len(linea)): # Incremento el numero de columna num_columna=i+1 # Saltar elementos if (saltar>0): saltar=saltar-1 continue # Compruebo si es un comentario o script if ((not script) and (linea[i:i+4]=='')): comentario=False saltar=2 continue elif ((not comentario) and (linea[i:i+7]=='')): script=False saltar=8 continue # Compruebo el caracter leido if ((not comentario) and (not script)): if (linea[i]=='<'): # Abro el tag if (leyendo_tag): raise IOError,"El tag ya estaba abierto, error en el contenido de la web. (Fila: %s, Columna: %s)\nFila %s: %s\nFila %s: %s" % (num_fila,num_columna,num_fila-1,linea_old,num_fila,linea) else: # Añado el elemento al contenido e inicializo el elemento if (self.contenido_valido(elemento)): elemento=self.filtrar(elemento) contenido.append(elemento.strip(" ")) elemento="" # Indico que estamos leyendo un tag leyendo_tag=True tag="" elif (linea[i]=='>'): # Cierro el tag if (not leyendo_tag): raise IOError,"El tag ya estaba cerrado, error en el contenido de la web. (Fila: %s, Columna: %s)\nFila %s: %s\nFila %s: %s" % (num_fila,num_columna,num_fila-1,linea_old,num_fila,linea) else: # Indico que ya no estamos leyendo un TAG leyendo_tag=False # Analizo el tag y lo proceso si es neceario tag_split=tag.split(" ") tag_first=True tipo_tag="" elementos_tag=[] for token in tag_split: if (tag_first): tipo_tag=token tag_first=False else: elementos_tag.append(token) # Analizo el resultado del tag if (tipo_tag.lower()=="img"): for token in elementos_tag: (token_ini,token_body_tmp)=token.split("=") token_body=token_body_tmp.split("\"") if (len(token_body)>1): token_body=token_body[1] if (token_ini.lower()=="src"): if (self.contenido_valido(token_body)): contenido.append(token_body) break # Inicializa el elemento elemento="" else: # Si es otro caracter if (leyendo_tag): tag="%s%s" % (tag,linea[i]) else: # Si no es un caracter que deba evitar if ((linea[i]!='\n') and (linea[i]!='\r')): elemento="%s%s" % (elemento,linea[i]) # Recuerdo la linea anterior para debuging linea_old=linea # Si quedó algún elemento por escribir if (elemento!=""): # Añado el elemento al contenido if (self.contenido_valido(elemento)): elemento=self.filtrar(elemento) contenido.append(elemento.strip("")) # Si tengo que hacer debug if (self.valor("debug_partselect")): self.debug("") self.debug("### TOTAL ### ######################") self.debug(contenido) self.debug("") # Devuelvo el contenido return contenido # }}}1 # Selecciona el contenido de una particion seguin los filtros indicados {{{1 ## Selecciona contenido segun los filtros indicados de una particion web ## \param self - ## \param tabla Contenido web particionado ## \param filtros Lista de filtros a procesar (el filtrado se aplica empezando por el principio de la lista) def selecciona(self,tabla,filtros=[]): # Inicializo los valores indice_sup=0 indice_inf=len(tabla) # Si hay que hacer debug if (self.valor("debug_partselect")): contador=1 self.debug("") # Para cada filtro for filtro in filtros: if (type(filtro)==type(3)): # Hago debug si se ha solicitado if (self.valor("debug_partselect")): self.debug("--- Filtro %s --- ------------------" % (contador)) self.debug(tabla[indice_sup+filtro]) self.debug("") # Si es un numero selecciono el elemento return [tabla[indice_sup+filtro]] elif (type(filtro)==type(())): # Si es una tupla extraigo los limites (inicio,fin,longitud)=filtro # Selecciono por encima if (inicio!=None): i=0 encontrado=False for elemento in tabla[indice_sup:indice_inf]: if (elemento==inicio): indice_sup=indice_sup+i+1 encontrado=True break i=i+1 if (not encontrado): raise StructureError,"No se ha encontrado el filtro izquierdo '%s' en la web '%s'" % (inicio,self.valor("url")) # Selecciono por debajo if (fin!=None): i=0 encontrado=False for elemento in tabla[indice_sup:indice_inf]: if (elemento==fin): indice_inf=indice_sup+i encontrado=True break i=i+1 if (not encontrado): raise StructureError,"No se ha encontrado el filtro derecho '%s' en la web '%s'" % (fin,self.valor("url")) # Test de longitud del resultado del filtrado if ((longitud!=None) and (longitud!=indice_inf-indice_sup)): raise StructureError,"No se cumple el test de longitud '%s' en la web '%s', se ha obtenido %s en el test" % (filtro,self.valor("url"),indice_inf-indice_sup) else: # Si no es una tupla ni un numero raise IOError,"El filtro contiene un elemento que no es un numero o una tupla" # Hago debug si se ha solicitado if (self.valor("debug_partselect")): self.debug("--- Filtro %s --- ------------------" % (contador)) self.debug(tabla[indice_sup:indice_inf]) self.debug("") contador=contador+1 # Devuelvo lo que quede return tabla[indice_sup:indice_inf] # }}}1 # Inicia una señal {{{ ## Inicializa una senal ## \param self - ## \param nombre Nombre de la senal a crear ## \param limpiadora Funcion encargada de limpiar el resultado ## \param interprete Funcion encargada de interpretar el resultado def signal(self,nombre,limpiadora=None,interprete=None): # Añado el filtro vacio self.__filtros.append((nombre,[],limpiadora,interprete)) # }}}1 # Añade un filtro nuevo en una señal {{{1 ## Inserta un filtro nuevo en una senal ## \param self - ## \param origen Donde comienza la cadena a recoger ## \param destino Donde termina la cadena a recoger ## \param longitud Longitud del resultado despues de filtrar ## \param nombre_senal Senal en la que insertar este filtro (Si no se indica nada, se insertara en la ultima senal creada) def addfilter(self,origen,destino=None,longitud=None,nombre_senal=None): # Comprueba si podemos trabajar if (len(self.__filtros)==0): # Error porque no se ha inciado la señal raise IOError,"No ha creado una senal a la que aplicar los filtros" else: # Si se indico senal la busco en la lista y obtengo su indice if (nombre_senal==None): # Pongo el indice del ultimo elemento indice=-1 else: # Busca la senal en la lista indice=None indice_temp=0 for temp in self.__filtros: if (temp[0]==nombre_senal): indice=indice_temp break else: indice_temp=indice_temp+1 # Comprueba que se encontro la senal if (indice==None): # La señal no se encontró raise IOError,"La senal '%s' indicada en el filtro (%s,%s) no se ha encontrado en la lista de senales" % (nombre_senal,origen,destino) # Añado el filtro if (type(origen)==type(3)): self.__filtros[indice][1].append(origen) else: self.__filtros[indice][1].append((origen,destino,longitud)) # }}}1 # Añade un limpiador nuevo en una senal {{{1 ## Carga la funcion de limpieza a una senal ## \param self - ## \param limpiadora Es la funcion de limpieza que se va a usar ## \param nombre_senal Senal en la que insertar este filtro (Si no se indica nada, se insertara en la ultima senal creada) def cleaner(self,limpiadora,nombre_senal=None): # Comprueba si podemos trabajar if (len(self.__filtros)==0): # Error porque no se ha inciado la señal raise IOError,"No ha creado una senal a la que aplicar los filtros" else: # Si se indico senal la busco en la lista y obtengo su indice if (nombre_senal==None): # Pongo el indice del ultimo elemento indice=-1 else: # Busca la senal en la lista indice=None indice_temp=0 for temp in self.__filtros: if (temp[0]==nombre_senal): indice=indice_temp break else: indice_temp=indice_temp+1 # Comprueba que se encontro la senal if (indice==None): # La señal no se encontró raise IOError,"La senal '%s' indicada al insertar el limpiador %s no se ha encontrado en la lista de senales" % (nombre_senal,limpiadora) # Añado la funcion de limpieza self.__filtros[indice]=(self.__filtros[indice][0],self.__filtros[indice][1],limpiadora,self.__filtros[indice][3]) # }}}1 # Añade un interprete nuevo en una senal {{{1 ## Carga la funcion de interpretacion una senal ## \param self - ## \param interprete Es la funciona que se encarga de interpretar el resultado ## \param nombre_senal Senal en la que insertar este filtro (Si no se indica nada, se insertara en la ultima senal creada) def reader(self,interprete,nombre_senal=None): # Comprueba si podemos trabajar if (len(self.__filtros)==0): # Error porque no se ha inciado la señal raise IOError,"No ha creado una senal a la que aplicar los filtros" else: # Si se indico senal la busco en la lista y obtengo su indice if (nombre_senal==None): # Pongo el indice del ultimo elemento indice=-1 else: # Busca la senal en la lista indice=None indice_temp=0 for temp in self.__filtros: if (temp[0]==nombre_senal): indice=indice_temp break else: indice_temp=indice_temp+1 # Comprueba que se encontro la senal if (indice==None): # La señal no se encontró raise IOError,"La senal '%s' indicada al insertar el interprete %s no se ha encontrado en la lista de senales" % (nombre_senal,interprete) # Añado la funcion de limpieza self.__filtros[indice]=(self.__filtros[indice][0],self.__filtros[indice][1],self.__filtros[indice][2],interprete) # }}}1 # Descarga de los Telemandos {{{1 ## \internal ## Descarga los datos de los Telemandos ## \param self - def download_extendido(self): # Lee la particion de la web particion=self.leeweb() # Para cada señal for senal in self.__filtros: # Descompongo la senal (nombre,filtros,limpiadora_txt,interprete_txt)=senal # Selecciono los datos dato_sucio=self.selecciona(particion,filtros)[0] # Limpio el resultado if (limpiadora_txt!=None): limpiadora=eval(limpiadora_txt) dato_tonto=limpiadora(dato_sucio) # Muestro la informacion de debug if (self.valor("debug_partselect")): self.debug("--- Limpiador --- ------------------") self.debug("Funcion: %s" % (limpiadora_txt)) self.debug("Dato sucio: %s" % (dato_sucio)) self.debug("Dato limpio: %s" % (dato_tonto)) self.debug("") else: dato_tonto=dato_sucio # Interpreto el resultado if (interprete_txt!=None): interprete=eval(interprete_txt) dato=interprete(dato_tonto) # Muestro la informacion de debug if (self.valor("debug_partselect")): self.debug("--- Interprete --- ------------------") self.debug("Funcion: %s" % (interprete_txt)) self.debug("Dato tonto: %s" % (dato_tonto)) self.debug("Dato intrepretado: %s" % (dato)) self.debug("") else: dato=dato_tonto # Proceso el dato recibido if (self.valor("debug_partselect")): self.debug("%s: %s" % (nombre,dato)) # Recojo el viaje temporal si coincide o registro si no if (self.valor("viaje_temporal")==nombre): # Cargo el viaje temporal self.__viaje_temporal_newdatetime=dato else: # Guardo el resultado self.__REGISTRADOR.register(nombre,"data",dato) # }}}1 # Control de descarga de los Telemandos {{{1 ## Control de descarga de los Telemandos (esta clase simplifica el proceso de recolectar Excepciones) ## \param self - def download(self): self.debug("Descargando datos para el Telemando '%s'" % (self.internal_id())) if (self.valor("debug_partselect")): self.debug("URL: %s" % (self.valor("url"))) try: # Consigue la info de la web self.download_extendido() # Escribe el resultado a disco if (self.__viaje_temporal_newdatetime!=None): # Recojo el nuevo valor de tiempo newdatetime=self.__viaje_temporal_newdatetime else: # Se deja trabajar normalmente newdatetime=None self.__REGISTRADOR.write(True,newdatetime) except ConnectionError,e: # Registro el error self.__REGISTRADOR.register_error("ERROR_NET") # Relanzo la excepción raise except IOError,e: # Error definitivo de entrada/salida # Si hay error al sincronizar, no se valida el TELEMANDO (podría disponer del resultado en la BD) # ANULADO: raise InvalidarTelemando, "%s" % (e) self.debug("El Telemando %s no ha sincronizado debido a un error de Entrada/Salida (%s)" % (self.internal_id(),e)) # Relanzo la excepción raise self.debug("") # }}}1 # EXCEPTION CLASSES {{{1 # Excepciones básicas # Except (General Exception) {{{2 class Except(Exception): def __init__(self,string): self.string=string def __str__(self): return self.string #}}}2 # ConnectionError (Conexión errónea al servidor) {{{2 class ConnectionError(Exception): def __init__(self,string): self.string=string def __str__(self): return self.string # }}}2 # IOError (Error de entrada/salida) {{{2 class IOError(Exception): def __init__(self,string): self.string=string def __str__(self): return self.string # }}}2 # ExecutionError (Error de ejecucción) {{{2 class ExecutionError(Exception): def __init__(self,string): self.string=string def __str__(self): return self.string # }}}2 # StructureError (Error de estructura) {{{2 class StructureError(Exception): def __init__(self,string): self.string=string def __str__(self): return self.string # }}}2 # Excepciones graves # TerminalError (Error irrecuperable del programa) {{{2 class TerminalError(Exception): def __init__(self,string): self.string=string def __str__(self): return self.string # }}}2 # }}}1