1. Introdução

No texto Gerador Básico de CSV, você aprendeu as funcionalidades básicas para criar um gerador de CSV, utilizando ferramentas básicas de programação.

  1. Definir um Modelo de Dados;
  2. Leitura dos arquivos que contém os dados extraídos;
  3. Extrair os dados definidos no modelo;
  4. Limpeza dos dados;
  5. Gravar os dados em uma tabela.

Para seguir adiante, você deve ter sido capaz de gerar uma tabela ADI.csv, a partir do código trabalhado naquele texto.

2. Campos compostos por listas

No presente texto, passamos a um segundo nível de complexidade: extrair dados que não são unitários, mas que são conjuntos de dados. Nos dados unitários, você pode extrair strings menores (como no caso do incidente ou da classe), ou strings maiores (como no caso da legislação impugnada ou da decisão monocrática).

Porém, há vários dados processuais que não são objetos simples, mas conjuntos de objetos, tais como:

  1. Partes de um processo;
  2. Andamentos processuais;
  3. Decisões;
  4. Recursos.

Cada processo não tem apenas um andamento (como ele tem uma e apenas uma data de distribuição e um e apenas um relator atual). Assim, na página em que o STF apresenta os andamentos processuais, cada processo tem sempre uma multiplicidade de andamentos.

Essa pluralidade de objetos no mesmo campo faz com que as strings que conseguimos extrair do Código Fonte nos ofereçam dados que não se deixam traduzir diretamente em informações úteis: precisamos estruturar esses dados para que o valor do campo andamentos seja uma lista organizada, que possa servir como fonte para as nossas análises.

Se trabalhássemos com bancos de dados relacionais mais complexos (com várias tabelas interligadas), poderíamos criar uma tabela para cada processo e inserir em cada linha a unidade andamento processual. Essa é uma possibilidade efetiva, mas ela agrega demasiada complexidade para os nossos objetivos, que são o de conseguir gerar dados adequados para a pesquisa empírica em direito, com o mínimo possível de ferramentas.

A estratégia que usamos aqui é a de reduzir a complexidade dos conjuntos de dados a uma lista, que tem por objeto os andamentos. Essa é uma saída que faz com que o nosso banco seja relacional (porque é uma tabela), mas ele terá campos complexos (compostos por listas), que demandam uma camada extra de interpretação para que essas informações possam ser usadas nas ferramentas de análise de dados.

Essa estratégia nos faz ter objetos complexos, com informações muito ricas, mas cuja compreensão depende de uma interpretação cuidadosa.

2.1 Listas de Listas

No Extrator Básico de Dados Judiciais, tratava-se de ligar várias informações simples a uma unidade básica, o processo. Agora, temos um novo nível de complexidade, pois os processos são ligados a informações também compostas por um conjunto de dados interligados.

No caso dos andamentos e das partes, temos uma elevada complexidade, pois cada um dos campos (Requerente, Requerido, Julgamento final, Decisão liminar, etc.) tem uma série de atributos (nome, tipo, relator, órgão  decisor, etc).

O conjunto dos andamentos de um processo apenas não é uma lista, mas é uma lista de listas:  cada andamento contém um conjunto de informações, e não apenas uma informação unitária.

Uma decisão monocrática pode ser uma string longa e complexa, mas podemos gravá-la como um campo simples. Mas todo andamento processual, por mais singelo que seja (como uma autuação), consiste em uma composição de múltiplos atributos, o que faz com que precisemos contar com um modelo de dados complexo. Temos unidades de análise (processos), que têm atributos complexos (uma lista de andamentos), sendo que cada andamento precisa também de um modelo de dados particular (porque ele é uma composição de atributos).

Essa peculiaridade faz com que, antes de entrar propriamente na extração dos atributos, seja necessário compreender com precisão o modo de constituição das listas e as formas de construir listas mediante iteração de comandos.

3. As listas em Python

3.1 Estrutura das listas

Como foi estudado no Módulo 2 do Curso de Programação para juristas, as listas são estruturas do Python que constituem exatamente no que o nome indica: uma sequência de elementos que têm duas características básicas:

  1. O início e o fim da lista são marcados por colchetes,
  2. Os elementos são elementos separados por vírgulas.

Uma lista de ministros do STF teria o seguinte formato em  julho de 2020:

MinistrosSTF = ['Luiz Fux', 'Rosa Weber', 'Marco Aurélio', 'Gilmar Mendes',  'Ricardo Lewandowski', 'Cármen Lúcia', 'Dias Toffoli', 'Roberto Barroso', 'Edson Fachin', 'Alexandre de Moraes']

Uma das vantagens da lista é que ela é uma conjunto dos elementos mais diversos: pode-se combinar dentro delas strings, variáveis, números e, inclusive, outras listas. Tudo o que você quiser, pode entrar numa lista, o que faz com que elas sejam ótimas para armazenar conjuntos heterogêneos de dados, como é o caso de um andamento processual.

Outra característica da lista é que ela ordena os elementos e confere a cada um deles uma posição:

  1. MinistrosSTF[1] indica o segundo elemento da lista;
  2. MinistrosSTF[0] indica o primeiro elemento da lista;
  3. MinistrosSTF[2] indica o terceiro elemento da lista;
  4. MinistrosSTF[-1] indica o último elemento da lista;
  5. MinistrosSTF[-2] indica o penúltimo elemento da lista.

Essa notação é muito prática e, não por acaso, ela se parece muito com a notação das strings. A diferença é que a string é um conjunto de caracteres, de tal forma que string[0] indica o primeiro caractere de uma sequência, enquanto a lista é um conjunto de objetos, sendo que lista[0] indica o segundo elemento da sequência.

O único problema dessa notação é que ela é a principal causa de os meus programas por vezes travarem por erro. Se inserirmos em um código o MinistrosSTF[10], ele vai travar e indicar que houve erro porque o código faz referência a um elemento fora do range da lista. Isso costuma ocorrer quando fazemos um cálculo de que o Tribunal tem 11 ministros, mas esquecemos de que um deles pode se aposentar e a corte pode ficar apenas com os elementos de 0 a 9.

3.2 Funções pop() e append()

Para lidar com as listas, existem várias ferramentas. No Gerador de CSV, utilizamos o listdir() para gerar uma lista dos processos em um diretório, que usamos para fazer iterações. Como nosso objetivo é utilizar o mínimo de ferramentas, vamos aprender apenas mais dois comandos para gerir listas.

A função mais importante é o append, que significa literalmente apensar, anexar. No Python, um comando do tipo lista.append(objeto) acrescenta o objeto entre parênteses ao final de uma lista. A posse do min. Nunes Marques exigiu que acrescentemos um novo nome, o que poderemos fazer pelo comando:

MinistrosSTF = ['Luiz Fux', 'Rosa Weber', 'Marco Aurélio', 'Gilmar Mendes',  'Ricardo Lewandowski', 'Cármen Lúcia', 'Dias Toffoli', 'Roberto Barroso', 'Edson Fachin', 'Alexandre de Moraes']

MinistrosSTF.append('Nunes Marques')

print (MinistrosSTF)

Para retirar o último elemento da lista, você pode usar a construção:

MinistrosSTF = MinistrosSTF[0:-1]

Uma outra função interessante é a pop(), que exclui um elemento da lista e retorna o elemento retirado.

ultimo_ministro = MinistrosSTF.pop()

A função pop() possibilita uma escrita mais concisa do código:

ultimo_ministro = MinistroSTF[-1]
MinistrosSTF = MinistrosSTF[0:-1]

4. O campo de Partes

4.1 Extração da string com todas as partes

Uma das utilizações importante das listas é na organização do campo Partes. Nas ADIs, são possíveis várias partes: Requerentes, Amici Curiae, Interessados (que antes eram chamados de Requeridos), Advogados. Se você aprender a organizar uma lista com esses dados, conseguirá também realizar o mesmo com o campo andamentos, que é um pouco mais complexo.

Vamos iniciar buscando as informações sobre partes da ADI 6000, extraída pelo nosso Extrator STF Geral. Abra o arquivo ADI6000.html e note que as informações sobre as partes contêm dois conjuntos diferentes: <div id="todas-partes">, que tem as informações completas, e <div id="partes-resumidas">, que tem um conjunto de partes selecionadas (excluindo os amici curiae).

Como as informações do segundo conjunto estão contidos no primeiro, vamos lidar com apenas esses dados, que podem ser extraídos com ferramentas que vocês já dominam:

  1. dsd.carregar_arquivo()
  2. dsd.extrair()
import dsd

# carrega dados do arquivo
html = dsd.carregar_arquivo('ADItotal\\ADI6000.html')

# extrai as informações totais das partes
partes = dsd.extrair(html,'partes>>>>', '<div id="partes-resumidas">')

print (partes)

O código acima deve gerar para você o seguinte resultado:

<div id="todas-partes">

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">REQTE.(S)</div>

            <div class="nome-parte">GOVERNADOR DO ESTADO DO RIO DE JANEIRO&nbsp</div>

        </div>

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">ADV.(A/S)</div>

            <div class="nome-parte">ESTADO DO RIO DE JANEIRO&nbsp</div>

        </div>

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">INTDO.(A/S)</div>

            <div class="nome-parte">ASSEMBLEIA LEGISLATIVA DO ESTADO DO RIO DE JANEIRO&nbsp</div>

        </div>

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">ADV.(A/S)</div>

            <div class="nome-parte">SEM REPRESENTAÇÃO NOS AUTOS&nbsp</div>

        </div>

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">AM. CURIAE.</div>

            <div class="nome-parte">ASSOCIAÇÃO DOS SERVIDORES DO MINISTÉRIO PÚBLICO DO ESTADO DO RIO DE JANEIRO - ASSEMPERJ&nbsp</div>

        </div>

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">ADV.(A/S)</div>

            <div class="nome-parte">RUDI MEIRA CASSEL (22256/DF)&nbsp</div>

        </div>

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">AM. CURIAE.</div>

            <div class="nome-parte">SINDICATO DOS SERVIDORES DO PODER JUDICIÁRIO DO ESTADO DO RIO DE JANEIRO -SINDJUSTIÇA&nbsp</div>

        </div>

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">ADV.(A/S)</div>

            <div class="nome-parte">RAIMUNDO CEZAR BRITTO ARAGAO (32147/DF, 140251/MG, 1190/SE, 439314/SP)&nbsp</div>

        </div>

    

        <div class="processo-partes lista-dados">

            <div class="detalhe-parte">ADV.(A/S)</div>

            <div class="nome-parte">PAULO FRANCISCO SOARES FREIRE (50755/DF)&nbsp</div>

        </div>

    

</div>

4.1 Transformar a string em uma lista tendo cada parte como elemento

Observando a string acima, você percebe que há várias partes e que existe um marcador que indica o início de cada uma delas:

        <div class="processo-partes lista-dados">

A presença desse marcador permite que utilizemos uma estratégia de segmentação da string, que seja capaz de gerar uma lista com todas as partes, a partir da função split(), que gera uma lista, segmentando a string em cada ocorrência do argumento.

        partes = partes.split('<div class="processo-partes lista-dados">')

Note que a função acima realiza uma segmentação da string partes, gerando uma lista com todas as partes do processo. Com isso, esse algoritmo faz com  que a variável partes deixa de ser uma string e passa a ser uma lista.

Experimente rodar o seguinte algoritmo no Spyder:

import dsd

# carrega dados do arquivo
html = dsd.carregar_arquivo('ADItotal\\ADI6000.html')

# extrai as informações totais das partes
partes = dsd.extrair(html,'partes>>>>', '<div id="partes-resumidas">')

partes = partes.split('<div class="processo-partes lista-dados">')

print (partes)

O resultado do print será uma lista muito poluída:

['\n\n\n\n<div id="todas-partes">\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">REQTE.(S)</div>\n\n            <div class="nome-parte">GOVERNADOR DO ESTADO DO RIO DE JANEIRO&nbsp</div>\n\n        </div>\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">ADV.(A/S)</div>\n\n            <div class="nome-parte">ESTADO DO RIO DE JANEIRO&nbsp</div>\n\n        </div>\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">INTDO.(A/S)</div>\n\n            <div class="nome-parte">ASSEMBLEIA LEGISLATIVA DO ESTADO DO RIO DE JANEIRO&nbsp</div>\n\n        </div>\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">ADV.(A/S)</div>\n\n            <div class="nome-parte">SEM REPRESENTAÇÃO NOS AUTOS&nbsp</div>\n\n        </div>\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">AM. CURIAE.</div>\n\n            <div class="nome-parte">ASSOCIAÇÃO DOS SERVIDORES DO MINISTÉRIO PÚBLICO DO ESTADO DO RIO DE JANEIRO - ASSEMPERJ&nbsp</div>\n\n        </div>\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">ADV.(A/S)</div>\n\n            <div class="nome-parte">RUDI MEIRA CASSEL (22256/DF)&nbsp</div>\n\n        </div>\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">AM. CURIAE.</div>\n\n            <div class="nome-parte">SINDICATO DOS SERVIDORES DO PODER JUDICIÁRIO DO ESTADO DO RIO DE JANEIRO -SINDJUSTIÇA&nbsp</div>\n\n        </div>\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">ADV.(A/S)</div>\n\n            <div class="nome-parte">RAIMUNDO CEZAR BRITTO ARAGAO (32147/DF, 140251/MG, 1190/SE, 439314/SP)&nbsp</div>\n\n        </div>\n\n    \n\n        ', '\n\n            <div class="detalhe-parte">ADV.(A/S)</div>\n\n            <div class="nome-parte">PAULO FRANCISCO SOARES FREIRE (50755/DF)&nbsp</div>\n\n        </div>\n\n    \n\n</div>\n\n']

Para evitar isso, basta inserir a função dsd.limpar(), que gera uma string mais limpa a ser segmentada. A dsd.limpar() deve ser usada com cuidado porque ela pode alterar os seus marcadores de início, de fim e de segmentação, algo que não ocorre neste caso em particular, mas que pode ocorrer em outras situações.

Veja que a função dsd.limpar() só opera sobre strings e não sobre listas. Para limpar uma lista de strings, seria preciso fazer um iterador que limpasse item a item e que tornaria o programa mais complexo. Por isso, a opção mais fácil é usar o limpar antes de usar o split:

import dsd

# carrega dados do arquivo
html = dsd.carregar_arquivo('ADItotal\\ADI6000.html')

# extrai as informações totais das partes
partes = dsd.extrair(html,'partes>>>>', '<div id="partes-resumidas">')
partes = dsd.limpar(partes)

partes = partes.split('<div class="processo-partes lista-dados">')

print (partes)

Esse código gera uma lista com os códigos HTML, mas que tem uma visualização mais simples. Poderíamos limpar esses códigos, mas ainda não é hora de fazê-lo, pois as formatações do HTML nos servem de marcadores para extrair os dados. Para facilitar a visualização, inserimos um parágrafo depois de cada vírgula.

['<div id="todas-partes">  ', 
 
 '  <div class="detalhe-parte">REQTE.(S)</div>  <div class="nome-parte">GOVERNADOR DO ESTADO DO RIO DE JANEIRO&nbsp</div>  </div>  ',
 
 '  <div class="detalhe-parte">ADV.(A/S)</div>  <div class="nome-parte">ESTADO DO RIO DE JANEIRO&nbsp</div>  </div>  ', 
 
 '  <div class="detalhe-parte">INTDO.(A/S)</div>  <div class="nome-parte">ASSEMBLEIA LEGISLATIVA DO ESTADO DO RIO DE JANEIRO&nbsp</div>  </div>  ',
 
 '  <div class="detalhe-parte">ADV.(A/S)</div>  <div class="nome-parte">SEM REPRESENTAÇÃO NOS AUTOS&nbsp</div>  </div>  ',
 
 '  <div class="detalhe-parte">AM. CURIAE.</div>  <div class="nome-parte">ASSOCIAÇÃO DOS SERVIDORES DO MINISTÉRIO PÚBLICO DO ESTADO DO RIO DE JANEIRO - ASSEMPERJ&nbsp</div>  </div>  ',
 
 '  <div class="detalhe-parte">ADV.(A/S)</div>  <div class="nome-parte">RUDI MEIRA CASSEL (22256/DF)&nbsp</div>  </div>  ',
 
 '  <div class="detalhe-parte">AM. CURIAE.</div>  <div class="nome-parte">SINDICATO DOS SERVIDORES DO PODER JUDICIÁRIO DO ESTADO DO RIO DE JANEIRO -SINDJUSTIÇA&nbsp</div>  </div>  ',
 
 '  <div class="detalhe-parte">ADV.(A/S)</div>  <div class="nome-parte">RAIMUNDO CEZAR BRITTO ARAGAO (32147/DF, 140251/MG, 1190/SE, 439314/SP)&nbsp</div>  </div>  ',
 
 '  <div class="detalhe-parte">ADV.(A/S)</div>  <div class="nome-parte">PAULO FRANCISCO SOARES FREIRE (50755/DF)&nbsp</div>  </div> </div>']

Observando os elementos da lista, você pode perceber duas coisas:

  1. O primeiro elemento não contem uma parte, pois estava antes do marcador.
  2. Todos os elementos que contém partes têm a mesma estrutura, o que nos oferece marcadores de início e fim padronizados.
  3. Os elementos contidos em cada parte são: tipo e nome.

Com isso, podemos usar a função dsd.extrair(), velha conhecida, para extrair de cada uma dessas partes as informações relevantes.

tipo:
marcador de início: 'detalhe-parte">'
marcador de fim: 	'</div>'

nome:
marcador de início:	'"nome-parte">'
marcador de fim:	'&nbsp'

Essas informações permitem que criemos uma função dsd.extrair_partes(), capaz de usar os marcadores acima definidos para transformar uma string extraída diretamente do código fonte em uma lista de partes, formada por listas com os elementos de cada uma das partes.

Lobo abaixo segue uma cópia do código dessa função, no dsd.py. Você pode ver que ela toma a string que é indicada como argumento do comando e a segmenta com o marcador que identificamos, para gerar uma lista de partes (lista_partes). Usamos essa lista (excluindo o primeiro elemento), para fazer uma iteração, de forma que seja possível processar as informações de cada parte e inseri-las na lista_partes. Cada loop insere uma parte específica, com seus três campos: ordem, tipo e nome.

def extrair_partes(string):
    
    partes = string.split('<div class="processo-partes lista-dados">')
    n=0
    lista_partes = []
    
    for parte in partes[1:]:
        n = n+1
        ordem = n
        tipo = extrair(parte, 'detalhe-parte">', '</div>')
        nome = extrair(parte, '"nome-parte">', '&nbsp')
        lista_partes.append([ordem, tipo, nome])
        
    return lista_partes

Essa função permite a construção de um código simples para extrair os dados das partes:

import dsd
     
# carrega dados do arquivo
html = dsd.carregar_arquivo('ADItotal\\ADI6000.html')

# extrai as informações totais das partes
partes = dsd.extrair(html,'partes>>>>', '<div id="partes-resumidas">')

# extrai as partes da string
partes = dsd.extrair_partes(partes)

print (partes)

Esse algoritmo retornaria o seguinte resultado:

[[1, 'REQTE.(S)', 'GOVERNADOR DO ESTADO DO RIO DE JANEIRO'], [2, 'ADV.(A/S)', 'ESTADO DO RIO DE JANEIRO'], [3, 'INTDO.(A/S)', 'ASSEMBLEIA LEGISLATIVA DO ESTADO DO RIO DE JANEIRO'], [4, 'ADV.(A/S)', 'SEM REPRESENTAÇÃO NOS AUTOS'], [5, 'AM. CURIAE.', 'ASSOCIAÇÃO DOS SERVIDORES DO MINISTÉRIO PÚBLICO DO ESTADO DO RIO DE JANEIRO - ASSEMPERJ'], [6, 'ADV.(A/S)', 'RUDI MEIRA CASSEL (22256/DF)'], [7, 'AM. CURIAE.', 'SINDICATO DOS SERVIDORES DO PODER JUDICIÁRIO DO ESTADO DO RIO DE JANEIRO -SINDJUSTIÇA'], [8, 'ADV.(A/S)', 'RAIMUNDO CEZAR BRITTO ARAGAO (32147/DF, 140251/MG, 1190/SE, 439314/SP)'], [9, 'ADV.(A/S)', 'PAULO FRANCISCO SOARES FREIRE (50755/DF)']]

Note que não há uma padronização no uso dos acentos, que aparecem em certas partes (como a 4) e não aparece em outras (como a 3). Essa falta de padronização dificulta a análise dos dados, já que nossos programas são sensíveis a essas diferenças.

Para superar esse problema, utilizamos dentro da própria função extrair_partes() a função dsd.remover_acentos(), que retorna o texto trocando os caracteres acentuados por caracteres sem acento. Essa função é construída com base na biblioteca unicodedata, e a sua inserção faz com que o programa acima retorne o seguinte resultado:

[[1, 'REQTE.(S)', 'GOVERNADOR DO ESTADO DO RIO DE JANEIRO'], [2, 'ADV.(A/S)', 'ESTADO DO RIO DE JANEIRO'], [3, 'INTDO.(A/S)', 'ASSEMBLEIA LEGISLATIVA DO ESTADO DO RIO DE JANEIRO'], [4, 'ADV.(A/S)', 'SEM REPRESENTACAO NOS AUTOS'], [5, 'AM. CURIAE.', 'ASSOCIACAO DOS SERVIDORES DO MINISTERIO PUBLICO DO ESTADO DO RIO DE JANEIRO - ASSEMPERJ'], [6, 'ADV.(A/S)', 'RUDI MEIRA CASSEL (22256/DF)'], [7, 'AM. CURIAE.', 'SINDICATO DOS SERVIDORES DO PODER JUDICIARIO DO ESTADO DO RIO DE JANEIRO -SINDJUSTICA'], [8, 'ADV.(A/S)', 'RAIMUNDO CEZAR BRITTO ARAGAO (32147/DF, 140251/MG, 1190/SE, 439314/SP)'], [9, 'ADV.(A/S)', 'PAULO FRANCISCO SOARES FREIRE (50755/DF)']]

5. O campo andamentos

5.1 O modelo de dados

O campo mais importante e rico das páginas de acompanhamento processual é o dos andamentos. Esse é também o campo mais desafiador, dada a heterogeneidade das informações contidas nos andamentos processuais.

Observando o que fizemos no campo parte, você pode notar que o primeiro passo é fazer um modelo de dados para os andamentos. Quais são os campos que levantaremos com relação a cada andamento específico? Precisamos disso para poder gerar uma função capaz de retornar uma lista com os andamentos.

O primeiro passo é buscar andamentos que sejam complexos, pois os andamentos simples não têm todos os campos de informação preenchidos. Nesse caso, uma estratégia importante é fazer uma pesquisa da ADI6000 pelo navegador, para observar a diversidade dos andamentos e buscar alguns que sejam especialmente completos.

Essa análise nos mostra que somente três campos parecem ser comuns a todos os andamentos, que são indicados em verde na figura acima.

  • Data: todo andamento tem uma data;
  • Nome: todo andamento tem um nome, que aparece em negrito;
  • Número na lista: esse não é um campo com informações contidas diretamente na página, mas que pode ser construído tal como fizemos com os requerentes. Essa numeração dos andamentos é útil para a ordenação, pois o Python ordena facilmente os campos em ordem crescente (ou decrescente), mas é mais complexo ordená-los por data (especialmente porque o campo de data pode ter vários formatos). Neste caso, é importante ordenar os andamentos do último para o primeiro, pois a ordem de ocorrência é inversa.

Além disso, existem 3 campos que ocorrem apenas em alguns andamentos:

  • Link para download de arquivo: no caso dos andamentos da figura acima, são textos de decisões monocráticas;
  • Complemento: em letra normal, sem negrito. Enquanto o título do andamento é escolhido em uma lista de andamentos possíveis, o complemento parece ser escrito livremente por quem lança o andamento. Trata-se do elemento mais 'livre' do andamento, e por isso mesmo o mais heterogêneo.
  • Órgão julgador: no andamento "Embargos Rejeitados", aparece o nome do min. Alexandre de Moraes. Porém, quando avançamos na lista de andamentos, podemos identificar também outros conteúdos para esse mesmo campo, como no caso da decisão Procedente:

Essa imagem mostra o andamento da decisão final de mérito, que foi colegiada, de tal forma que não aparece o ministro relator, mas um órgão do STF. Isso permite que desdobremos as informações contidas nesse campo para nos indicarem dois dados diferentes:

  • Tipo de julgamento (colegiado, monocrático ou NA): esse é um campo que diferencia decisões de andamentos não-decisórios (que entram como NA), e que permite classificar se a decisão foi monocrática (quando aparece um ministro) ou se foi colegiada (quando aparece um órgão julgad0r).
  • Órgão julgador: ministro (no caso de monocráticas) ou órgão (no caso de colegiadas) que editou o andamento decisório. No caso de andamento não-decisório, o valor é NA.

O resultado dessa análise nos aponta para o seguinte modelo de dados:

  1. Posição: posição do andamento na lista
  2. Data: valor de uma data específica
  3. Nome: que segue uma lista finita de possibilidades
  4. Complemento: que é um texto livre
  5. Link: presente apenas em alguns casos
  6. Órgão julgador: com valores de ministros (nas monocráticas) ou de órgãos (nas colegiadas)
  7. Tipo de julgamento: com 3 valores (colegiado, monocrático ou NA)

5.2 Extraindo os andamentos

A extração dos andamentos pode ser feita por uma adaptação da mesma lógica da extração das partes: identificar o marcador para split e depois identificar os marcadores de início e fim de cada elemento. Essa extração é feita pela função dsd.extrair_andamentos.py, que pode ser inserida no nosso código:

import dsd
     
# carrega dados do arquivo
html = dsd.carregar_arquivo('ADItotal\\ADI6000.html')

# extrai as partes
partes = dsd.extrair(html,'partes>>>>', '<div id="partes-resumidas">')
partes = dsd.extrair_partes(partes)

# extrai os andamentos
andamentos = dsd.extrair(html,'andamentos>>>>', 'pauta>>>>')
andamentos = dsd.extrair_andamentos(andamentos)

print (partes)
print (andamentos)

6. Inserindo os demais dados

Embora ainda haja um número razoável de informações a serem extraída, as partes e os andamentos são as mais complexas, o que faz com que o trabalho a seguir seja mais simples, pois a maioria delas é de objetos unitários, e não de listas.

7.1 Código Fonte

No próprio código fonte, há várias informações a serem coletadas:

  1. Tipo de Processo: variável binária (Eletrônico, Físico)
  2. Sigilo: variável binária (Sigiloso, Público)
  3. Nome do processo: é um dado que você já tem, mas que pode ser útil coletar, para fins de avaliar a consistência dos dados.
  4. Número único: é um identificador que não é usado no STF, mas pode ser interessante em causas recursais
  5. Origem do processo: a informação não está no código fonte, mas na aba informações.
  6. Relator atual: indica o relator no momento da extração. Infelizmente, não há um campo que indique os relatores sucessivos.
  7. Redator do acórdão: importante por indicar processos em que o relator foi vencido
  8. Relator do último incidente: veja que, aqui, incidente é usado para tratar de incidentes processuais.

Essas são informações variadas, que você não deve ter dificuldade especial em coletar, utilizando a função extrair para isolar a parte "informações" e, dentro dela, identificar cada um dos campos acima definidos.

7.2 Aba informações

Na aba informações (informacoes>>>>), existem também alguns dados relevantes.

  1. Assunto: trata-se de uma lista, pois em alguns casos há vários assuntos.
  2. Procedência: nem sempre é preenchido
  3. Data de protocolo: data simples
  4. Órgão de Origem: no caso do controle concentrado, será sempre o STF
  5. Origem: Esse dado é repetido com o do código fonte
  6. Número de Origem: esse é um campo anterior à existência do número único, que vem inserido nesse campo nas ações recentes.
  7. Descrição procedência: nesse caso, vem a sigla do estado, não só o nome.

Com tudo o que você realizou até aqui, a inserção desses dados será simples. Tão simples que não passaremos nem um exercício sobre elas.

import dsd
     
# carrega dados do arquivo
html = dsd.carregar_arquivo('ADItotal\\ADI6000.html')

# extrai as partes
partes = dsd.extrair(html,'partes>>>>', '<div id="partes-resumidas">')
partes = dsd.extrair_partes(partes)

# extrai os andamentos
andamentos = dsd.extrair(html,'andamentos>>>>', 'pauta>>>>')
andamentos = dsd.extrair_andamentos(andamentos)


#extrai os elementos do código fonte
codigofonte =dsd.extrair(html,'fonte>>>>', 'partes>>>>')

eletronico_fisico =dsd.extrair(codigofonte,'bg-primary">','</span>')

sigilo =dsd.extrair(codigofonte,'bg-success">','</span>')

nome_processo =dsd.extrair(codigofonte,'-processo" value="','">')

numerounico  = dsd.extrair(codigofonte,'-rotulo">','</div>')

relator = dsd.extrair(codigofonte,'"Relator:','</div>')

redator_acordao = dsd.extrair(codigofonte,'>Redator do acórdão:','</div>')

relator_ultimo_incidente = dsd.extrair(codigofonte,
                                  'Relator do último incidente:'
                                  ,'</div>')


#extrai os elementos da aba informações
informacoes = dsd.extrair(html,'informacoes>>>>', '>>>>')

assuntos = dsd.extrair(informacoes, '<ul style="list-style:none;">', '</ul>')

procedencia = dsd.extrair(informacoes,'<div class="col-md-12 m-t-8 m-b-8">', '<div class="col-md-7 processo-detalhes-bold p-l-0">')

protocolo_data = dsd.extrair(informacoes, '<div class="col-md-5 processo-detalhes-bold m-l-0">', '</div>')

orgaodeorigem = dsd.extrair(informacoes, '''Órgão de Origem:
            </div>
            <div class="col-md-5 processo-detalhes">''', '</div>')

numerodeorigem = dsd.extrair(informacoes, '''Número de Origem:
            </div>
            <div class="col-md-5 processo-detalhes">''', '</div>')

origem  = dsd.extrair(informacoes, '''Origem:
            </div>
            <div class="col-md-5 processo-detalhes">''', '</div>')
            
procedencia = dsd.extrair(informacoes, '''<span id="descricao-procedencia">''', '</span>')

# extrai campos CC
if 'ADI' in nome_processo or 'ADPF' in nome_processo or 'ADC' in nome_processo or 'ADO' in nome_processo:
    
    cc = dsd.extrair(html, 'cc>>>','')

        # extrai campo incidente
    incidentecc = dsd.extrair (cc, 
                             'verProcessoAndamento.asp?incidente=',
                             '">')   
    
    # extrai campos classe + liminar + numero
    cln = dsd.extrair(cc, 
                      '<div><h3><strong>', 
                      '</strong>')
    
    # extrai numero
    numerocc = dsd.extrair (cln, ' - ', '')
    numerocc = dsd.limpar_numero(numerocc)
    
    # extrai liminar e classe    
    if 'Liminar' in cln:
        liminarcc = 'sim'
        classecc = dsd.extrair(cln, '', ' (Med') 
    else:
        liminar = 'não'
        classecc = dsd.extrair(cln, '', ' - ') 
    
    
    # definição de campo: origem     
    origemcc = dsd.extrair(cc,'Origem:</td><td><strong>','</strong>')
    
           
    ## definição de campo: entrada
    entradacc = dsd.extrair(cc,'Entrada no STF:</td><td><strong>','</strong>')
    
    ## definição de campo: relator
    relatorcc = dsd.extrair(cc,'Relator:</td><td><strong>','</strong>')
    relatorcc = relatorcc.replace('MINISTRO','')
    relatorcc = relatorcc.replace('MINISTRA','')
    
    
    ## definição de campo: distribuição
    distribuicaocc = dsd.extrair(cc,'Distribuído:</td><td><strong>','</strong>')
    
    
    ## definição de campo: requerente
    requerentecc = dsd.extrair(cc,'Requerente: <strong>','</strong>')
    if '(CF 103, ' in requerentecc:
        requerentesplit = requerentecc.split('(CF 103, ')
        requerente = requerentesplit[0]
        requerentecc = requerente.strip()
        requerentetipo = requerentesplit[1]
        requerentetipo = requerentetipo.replace(')','')
        requerentetipocc = requerentetipo.replace('0','')
    else:
        requerentetipocc = 'NA'
    
    ## definição de campo: requerido
    requeridocc = dsd.extrair(cc,
                        'Requerido :<strong>',
                        '</strong>')
    
    ## definição de campo: dispositivo questionado
    dispositivoquestionadocc = dsd.extrair(cc,
                                     'Dispositivo Legal Questionado</b></strong><br /><pre>',
                                     '</pre>')
    dispositivoquestionadocc = dsd.limpar(dispositivoquestionadocc)
    
    ## definição de campo: resultado da liminar
    resultadoliminarcc = dsd.extrair(cc,
                                   'Resultado da Liminar</b></strong><br /><br />',
                                   '<br />')
    
    ## definição de campo: resultado final
    resultadofinalcc = dsd.extrair(cc,
                                 'Resultado Final</b></strong><br /><br />',
                                 '<br />')
    
    ## definição de campo: decisão monocrática final
    if 'Decisão Monocrática Final</b></strong><br /><pre>' in cc:
        decisaomonofinal = dsd.extrair(cc,
                                       'Decisão Monocrática Final</b></strong><br /><pre>',
                                       '</pre>')
        decisaomonofinalcc = dsd.limpar(decisaomonofinal)
    else: 
        decisaomonofinalcc = 'NA'
         
    ## definição de campo: fundamento    
    if 'Fundamentação Constitucional</b></strong><br /><pre>' in cc:
        fundamentocc = dsd.extrair(cc,
                             'Fundamentação Constitucional</b></strong><br /><pre>',
                             '</pre>')
        fundamentocc = dsd.limpar(fundamentocc)
    else:
        fundamentocc = 'NA'
    
    ## definição de campo: indexação
    if 'Indexação</b></strong><br /><pre>' in cc:
        indexacaocc = dsd.extrair(cc,
                            'Indexação</b></strong><br /><pre>',
                            '</pre>')
        indexacaocc = dsd.limpar(indexacaocc)        
    else:
        indexacaocc = 'NA'
        
# criação da variável dados extraídos, com uma lista de dados
dados = [classecc, numerocc, incidentecc, requerente, 
         requerentetipo, requeridocc, len(partes),partes,len(andamentos),
         andamentos, codigofonte, eletronico_fisico, sigilo, nome_processo, 
         numerounico, relatorcc, relator, redator_acordao, 
         relator_ultimo_incidente, assuntos, procedencia, protocolo_data, 
         entradacc, distribuicaocc, orgaodeorigem, 
         numerodeorigem, origem, origemcc, procedencia,  
         liminarcc, dispositivoquestionadocc, resultadoliminarcc, resultadofinalcc, 
         decisaomonofinalcc, fundamentocc, indexacaocc]
#inserir aqui o conteúdo da lista acima, trocando [] por ''
campos = '''classecc, numerocc, incidentecc, requerente, 
         requerentetipo, requeridocc, len(partes),partes,len(andamentos),
         andamentos, codigofonte, eletronico_fisico, sigilo, nome_processo, 
         numerounico, relatorcc, relator, redator_acordao, 
         relator_ultimo_incidente, assuntos, procedencia, protocolo_data, 
         entradacc, distribuicaocc, orgaodeorigem, 
         numerodeorigem, origem, origemcc, procedencia,  
         liminarcc, dispositivoquestionadocc, resultadoliminarcc, resultadofinalcc, 
         decisaomonofinalcc, fundamentocc, indexacaocc'''

dsd.write_csv_header('ADItotal.csv',campos)
dsd.write_csv_line('ADItotal.csv',dados)
    
print ('Gravado arquivo ADItotal.csv')

7.3 Demais dados

Os demais dados não extrairemos agora, mas você pode tentar fazê-lo. Note que as decisões são andamentos com órgão decisor não vazio (o que já extraímos), que os deslocamentos formam também uma lista.

Os dados das Sessões são complexos e precisam de um modelo específico, que será desenvolvido no Extrator Avançado, bem como a inserção de dados da jurisprudência.