Extraer información de grandes archivos de texto estructurado

Necesito leer algunos archivos grandes (de 50k a 100k líneas), estructurados en grupos separados por líneas vacías. Cada grupo comienza con el mismo patrón “No.999999999 dd / mm / aaaa ZZZ”. Aquí hay algunos datos de muestra.

No. 813829461 16/09/1987 270
Tit.SUZANO PAPEL E CELULOSE SA (BR / BA)
CNPJ / CIC / N INPI: 16404287000155
Procurador: MARCELLO DO NASCIMENTO

No.815326777 28/12/1989 351
Tit.SIGLA SISTEMA GLOBO DE GRAVACOES AUDIO VISUAIS LTDA (BR / RJ)
CNPJ / CIC / NºINPI: 34162651000108
Apres .: Nominativa; Nat .: De Produto
Marca: TRIO TROPICAL
Clas.Prod/Serv: 09.40
* DEFERIDO CONFORME RESOLUÇÃO 123 DE 06/01/2006, PUBLICADA NA RPI 1829, DE 24/01/2006
Procurador: WALDEMAR RODRIGUES PEDRA

No.900148764 11/01/2007 LD3
Tit.TIARA BOLSAS E CALÇADOS LTDA.
Procurador: Marcia Ferreira Gomes
* Escritório: Marcas Marcantes e Patentes Ltda.
* Exigência Formal no respondida Satisfatoriamente, Pedido de Registro de Marca considerado inexistente, de acordo com Art. 157 da LPI
* Protocolo de la Petición de cumplimiento de la Ejecución Formal: 810080140197

Escribí un código que lo está analizando en consecuencia. ¿Hay algo que pueda mejorar para mejorar la legibilidad o el rendimiento? Esto es lo que vengo hasta ahora:

    import re, pprint class Despacho(object): """ Class to parse each line, applying the regexp and storing the results for future use """ regexp = { re.compile(r'No.([\d]{9}) ([\d]{2}/[\d]{2}/[\d]{4}) (.*)'): lambda self: self._processo, re.compile(r'Tit.(.*)'): lambda self: self._titular, re.compile(r'Procurador: (.*)'): lambda self: self._procurador, re.compile(r'CNPJ/CIC/N INPI :(.*)'): lambda self: self._documento, re.compile(r'Apres.: (.*) ; Nat.: (.*)'): lambda self: self._apresentacao, re.compile(r'Marca: (.*)'): lambda self: self._marca, re.compile(r'Clas.Prod/Serv: (.*)'): lambda self: self._classe, re.compile(r'\*(.*)'): lambda self: self._complemento, } def __init__(self): """ 'complemento' is the only field that can be multiple in a single registry """ self.complemento = [] def _processo(self, matches): self.processo, self.data, self.despacho = matches.groups() def _titular(self, matches): self.titular = matches.group(1) def _procurador(self, matches): self.procurador = matches.group(1) def _documento(self, matches): self.documento = matches.group(1) def _apresentacao(self, matches): self.apresentacao, self.natureza = matches.groups() def _marca(self, matches): self.marca = matches.group(1) def _classe(self, matches): self.classe = matches.group(1) def _complemento(self, matches): self.complemento.append(matches.group(1)) def read(self, line): for pattern in Despacho.regexp: m = pattern.match(line) if m: Despacho.regexp[pattern](self)(m) def process(rpi): """ read data and process each group """ rpi = (line for line in rpi) group = False for line in rpi: if line.startswith('No.'): group = True d = Despacho() if not line.strip() and group: # empty line - end of block yield d group = False d.read(line) arquivo = open('rm1972.txt') # file to process for desp in process(arquivo): pprint.pprint(desp.__dict__) print('--------------') 

    Eso es bastante bueno. A continuación algunas sugerencias, déjame saber si te gustan:

     import re import pprint import sys class Despacho(object): """ Class to parse each line, applying the regexp and storing the results for future use """ #used a dict with the keys instead of functions. regexp = { ('processo', 'data', 'despacho'): re.compile(r'No.([\d]{9}) ([\d]{2}/[\d]{2}/[\d]{4}) (.*)'), ('titular',): re.compile(r'Tit.(.*)'), ('procurador',): re.compile(r'Procurador: (.*)'), ('documento',): re.compile(r'CNPJ/CIC/N INPI :(.*)'), ('apresentacao', 'natureza'): re.compile(r'Apres.: (.*) ; Nat.: (.*)'), ('marca',): re.compile(r'Marca: (.*)'), ('classe',): re.compile(r'Clas.Prod/Serv: (.*)'), ('complemento',): re.compile(r'\*(.*)'), } def __init__(self): """ 'complemento' is the only field that can be multiple in a single registry """ self.complemento = [] def read(self, line): for attrs, pattern in Despacho.regexp.iteritems(): m = pattern.match(line) if m: for groupn, attr in enumerate(attrs): # special case complemento: if attr == 'complemento': self.complemento.append(m.group(groupn + 1)) else: # set the attribute on the object setattr(self, attr, m.group(groupn + 1)) def __repr__(self): # defines object printed representation d = {} for attrs in self.regexp: for attr in attrs: d[attr] = getattr(self, attr, None) return pprint.pformat(d) def process(rpi): """ read data and process each group """ #Useless line, since you're doing a for anyway #rpi = (line for line in rpi) group = False for line in rpi: if line.startswith('No.'): group = True d = Despacho() if not line.strip() and group: # empty line - end of block yield d group = False d.read(line) def main(): arquivo = open('rm1972.txt') # file to process for desp in process(arquivo): print desp # can print directly here. print('-' * 20) return 0 if __name__ == '__main__': main() 

    Sería más fácil ayudarlo si tuviera una inquietud específica. El rendimiento dependerá en gran medida de la eficiencia del motor de expresión regular en particular que esté utilizando. 100 mil líneas en un solo archivo no suenan tan grandes, pero nuevamente todo depende de su entorno.

    Utilizo Expresso en mi desarrollo .NET para probar expresiones de precisión y rendimiento. Una búsqueda en Google reveló Kodos , una herramienta de creación de expresiones regulares de Python GUI.

    Se ve bien en general, pero ¿por qué tienes la línea:

     rpi = (line for line in rpi) 

    Ya puede iterar sobre el objeto de archivo sin este paso intermedio.

    No usaría regex aquí. Si sabe que sus líneas comenzarán con cadenas fijas, ¿por qué no las comprueba y escribe una lógica a su alrededor?

     for line in open(file): if line[0:3]=='No.': currIndex='No' map['No']=line[4:] .... ... else if line.strip()=='': //store the record in the map and clear the map else: //append line to the last index in map.. this is when the record overflows to the next line. Map[currIndex]=Map[currIndex]+"\n"+line 

    Considere el código anterior como solo el pseudocódigo.

    Otra versión con una sola expresión regular combinada:

     #!/usr/bin/python import re import pprint import sys class Despacho(object): """ Class to parse each line, applying the regexp and storing the results for future use """ #used a dict with the keys instead of functions. regexp = re.compile( r'No.(?P[\d]{9}) (?P[\d]{2}/[\d]{2}/[\d]{4}) (?P.*)' r'|Tit.(?P.*)' r'|Procurador: (?P.*)' r'|CNPJ/CIC/N INPI :(?P.*)' r'|Apres.: (?P.*) ; Nat.: (?P.*)' r'|Marca: (?P.*)' r'|Clas.Prod/Serv: (?P.*)' r'|\*(?P.*)') simplefields = ('processo', 'data', 'despacho', 'titular', 'procurador', 'documento', 'apresentacao', 'natureza', 'marca', 'classe') def __init__(self): """ 'complemento' is the only field that can be multiple in a single registry """ self.__dict__ = dict.fromkeys(self.simplefields) self.complemento = [] def parse(self, line): m = self.regexp.match(line) if m: gd = dict((k, v) for k, v in m.groupdict().items() if v) if 'complemento' in gd: self.complemento.append(gd['complemento']) else: self.__dict__.update(gd) def __repr__(self): # defines object printed representation return pprint.pformat(self.__dict__) def process(rpi): """ read data and process each group """ d = None for line in rpi: if line.startswith('No.'): if d: yield d d = Despacho() d.parse(line) yield d def main(): arquivo = file('rm1972.txt') # file to process for desp in process(arquivo): print desp # can print directly here. print '-' * 20 if __name__ == '__main__': main()