├── .gitignore ├── tests ├── fixtures │ └── teste.pdf └── tests_leitor_pdf.py ├── requirements.txt ├── pdf.py └── extrai_entidades.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.py[co] 3 | -------------------------------------------------------------------------------- /tests/fixtures/teste.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/registros-ocorrencias/develop/tests/fixtures/teste.pdf -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | backcall==0.1.0 2 | chardet==3.0.4 3 | decorator==4.3.0 4 | ipython==7.2.0 5 | ipython-genutils==0.2.0 6 | jedi==0.13.2 7 | parso==0.3.1 8 | pdfminer.six==20181108 9 | pexpect==4.6.0 10 | pickleshare==0.7.5 11 | prompt-toolkit==2.0.7 12 | ptyprocess==0.6.0 13 | pycryptodome==3.7.2 14 | Pygments==2.3.1 15 | six==1.12.0 16 | sortedcontainers==2.1.0 17 | traitlets==4.3.2 18 | wcwidth==0.1.7 19 | -------------------------------------------------------------------------------- /tests/tests_leitor_pdf.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase, main 2 | 3 | from pdf import le_pdf 4 | 5 | 6 | class TestLeitorPdf(TestCase): 7 | def test_le_conteudo_file_obj(self): 8 | fobj = open('tests/fixtures/teste.pdf', 'rb') 9 | 10 | conteudo = le_pdf(fobj) 11 | esperado = 'Documento PDF\nConteúdo qualquer' 12 | 13 | self.assertEqual(conteudo, esperado) 14 | 15 | def test_le_conteudo_filename(self): 16 | filename = 'tests/fixtures/teste.pdf' 17 | 18 | conteudo = le_pdf(filename) 19 | esperado = 'Documento PDF\nConteúdo qualquer' 20 | 21 | self.assertEqual(conteudo, esperado) 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /pdf.py: -------------------------------------------------------------------------------- 1 | import io 2 | 3 | from pdfminer.converter import TextConverter 4 | from pdfminer.pdfdocument import PDFDocument 5 | from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter 6 | from pdfminer.pdfpage import PDFPage 7 | from pdfminer.pdfparser import PDFParser 8 | from pdfminer.layout import LAParams 9 | 10 | 11 | def get_filename_and_fobj(filename_or_fobj): 12 | if isinstance(filename_or_fobj, str): 13 | fobj = open(filename_or_fobj, 'rb') 14 | filename = filename_or_fobj 15 | else: 16 | fobj = filename_or_fobj 17 | filename = getattr(fobj, 'name', None) 18 | 19 | return filename, fobj 20 | 21 | 22 | def le_pdf(filename_or_fobj): 23 | filename, fobj = get_filename_and_fobj(filename_or_fobj) 24 | parser = PDFParser(fobj) 25 | doc = PDFDocument(parser) 26 | texto = '' 27 | for num_pagina, pagina in enumerate(PDFPage.create_pages(doc), start=1): 28 | rsrcmgr = PDFResourceManager() 29 | laparams = LAParams() 30 | resultado = io.StringIO() 31 | conversor = TextConverter(rsrcmgr, resultado, laparams=laparams) 32 | interpretador = PDFPageInterpreter(rsrcmgr, conversor) 33 | interpretador.process_page(pagina) 34 | texto += resultado.getvalue() 35 | 36 | return texto.strip() 37 | -------------------------------------------------------------------------------- /extrai_entidades.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from collections import namedtuple 4 | 5 | 6 | def prepara_documento(doc): 7 | return re.sub(r'\s+', ' ', doc) 8 | 9 | 10 | def extrai_blocos_envolvidos(doc): 11 | doc = prepara_documento(doc) 12 | resultado = re.finditer( 13 | r'((Testemunha|Autor|Lesado|V[ií]tima|Representante|Adolescente)\s+-|' 14 | '(Bem\(ns\)\s+' 15 | 'Envolvido\(s\)|Din[aâ]mica do Fato))', 16 | doc, 17 | re.IGNORECASE 18 | ) 19 | posicoes = [r.span()[0] for r in resultado] 20 | blocos = zip(posicoes[::1], posicoes[1::1]) 21 | 22 | return [doc[slice(*bloco)] for bloco in blocos] 23 | 24 | 25 | def extrai_nome_bloco(bloco): 26 | tipo = '' 27 | nome = '' 28 | 29 | nome_encontrado = re.search(r'(Nome:)\s?(.*?-)', bloco) 30 | if nome_encontrado is not None: 31 | nome = nome_encontrado.group(2).replace('-', '').strip() 32 | 33 | tipo_encontrado = re.match(r'.*?-', bloco) 34 | if tipo_encontrado is not None and nome: 35 | tipo = tipo_encontrado.group(0).replace('-', '').strip().lower() 36 | 37 | return {'nome': nome, 'tipo': tipo} 38 | 39 | 40 | def extrai_documento_bloco(bloco): 41 | tipo_obj = namedtuple('Tipo', ['tipo', 'regex']) 42 | docs = [ 43 | tipo_obj('identidade', '(Identidade Nº\s+)(\d+-?\d+)'), 44 | tipo_obj('cpf_cic', '(CPF/CIC Nº\s+)(\d{3}\.\d{3}\.\d{3}-\d{2})'), 45 | tipo_obj('carteira_funcional', '(Carteira funcional Nº\s+)([\d+.]+)') 46 | ] 47 | documentos = [] 48 | for doc in docs: 49 | encontrado = re.search(doc.regex, bloco) 50 | if encontrado is not None: 51 | documentos.append({ 52 | 'documento': encontrado.group(2), 53 | 'tipo': doc.tipo 54 | }) 55 | 56 | return {'documentos': documentos} 57 | 58 | 59 | def extrai_filiacao_bloco(bloco): 60 | filiacao = '' 61 | encontrado = re.search( 62 | '(Filho de:\s?)(.*?)\s+(?=Data)', 63 | bloco, 64 | re.IGNORECASE 65 | ) 66 | if encontrado is not None: 67 | filiacao = encontrado.group(2) 68 | 69 | return {'filiacao': filiacao} 70 | 71 | 72 | def extrai_dnascimento_bloco(bloco): 73 | data_nasc = '' 74 | encontrado = re.search( 75 | '(Data de nascimento:\s+)(\d{2}/\d{2}/\d{4})', 76 | bloco 77 | ) 78 | 79 | if encontrado is not None: 80 | data_nasc = encontrado.group(2) 81 | 82 | return {'data_nascimento': data_nasc} 83 | 84 | 85 | def extrai_naturalidade_bloco(bloco): 86 | naturalidade = '' 87 | encontrado = re.search( 88 | '(Naturalidade:\s+)(.*?)\s+(Nacionalidade:)', 89 | bloco 90 | ) 91 | if encontrado is not None: 92 | naturalidade = encontrado.group(2) 93 | 94 | return {'naturalidade': naturalidade} 95 | 96 | 97 | def extrai_informacoes_bloco(bloco): 98 | funcs = [ 99 | extrai_nome_bloco, 100 | extrai_documento_bloco, 101 | extrai_filiacao_bloco, 102 | extrai_dnascimento_bloco, 103 | extrai_naturalidade_bloco 104 | ] 105 | 106 | info = {} 107 | for fun in funcs: 108 | info.update(fun(bloco)) 109 | 110 | return info if info['nome'] else None 111 | 112 | 113 | def extrai_artigos(doc): 114 | # encontrado = re.finditer( 115 | # r'(art|art\.|artigos?)\s+(\d+).*?((do )?(inciso\d+|CPBO|C[oó]digo' 116 | # ' Penal|CP|CTBO|CT|C\.P|DL|C[óo]digo de processo Penal|Lei\s+[\d./]' 117 | # '+))', 118 | # doc, 119 | # re.IGNORECASE 120 | # ) 121 | encontrado = re.finditer( 122 | r'(Capitula[cç][aã]o:?\s+)(.*?)\n', 123 | doc, 124 | re.IGNORECASE 125 | ) 126 | return {'artigos': [artigo.group(2).strip() for artigo in encontrado]} 127 | 128 | 129 | # TODO: transforma essas funções de extracoes em uma função genérica 130 | def extrai_local(doc): 131 | local = '' 132 | encontrado = re.search(r'(Local:\s+)(.*?)(Bairro:)', doc, re.IGNORECASE) 133 | 134 | if encontrado is not None: 135 | local = encontrado.group(2).strip() 136 | 137 | return {'local': local} 138 | 139 | 140 | def extrai_bairro(doc): 141 | bairro = '' 142 | encontrado = re.search( 143 | r'(Bairro:\s+)(.*?)(Munic[ií]pio)', 144 | doc, 145 | re.IGNORECASE 146 | ) 147 | 148 | if encontrado is not None: 149 | bairro = encontrado.group(2).strip() 150 | 151 | return {'bairro': bairro} 152 | 153 | 154 | def extrai_municipio(doc): 155 | municipio = '' 156 | encontrado = re.search( 157 | r'(Munic[ií]pio:\s+)(.*?\s?-\s?RJ)', 158 | doc, 159 | re.IGNORECASE 160 | ) 161 | if encontrado is not None: 162 | municipio = encontrado.group(2).strip() 163 | 164 | return {'municipio': municipio} 165 | 166 | 167 | def extrai_data_hora(doc): 168 | data_hora = '' 169 | encontrado = re.search( 170 | r'(Data e Hora do fato:\s?)(\d{2}/\d{2}/\d{4}\s?\d{2}:\d{2}\s?[ae]?\s?' 171 | '\d{2}/\d{2}/\d{4}\s?\d{2}:\d{2})', 172 | doc, 173 | re.IGNORECASE 174 | ) 175 | 176 | if encontrado is not None: 177 | data_hora = encontrado.group(2) 178 | 179 | return {'data_hora': data_hora} 180 | 181 | 182 | def extrai_atributos_ocorrencias(doc): 183 | info = {} 184 | info.update(extrai_artigos(doc)) 185 | info.update(extrai_local(doc)) 186 | info.update(extrai_bairro(doc)) 187 | info.update(extrai_municipio(doc)) 188 | info.update(extrai_data_hora(doc)) 189 | 190 | return info 191 | --------------------------------------------------------------------------------