querido-diario
querido-diario copied to clipboard
Add Sao Luis - MA spider
Adicionado o spider para a capital do Maranhão — São Luis. Existe um PR aberto para isso (#22), mas esta sem alterações desde julho de 2018.
O spider do PR anterior adicionou uma biblioteca para execução de javascript, o Splash. Para rodar ele precisa subir um serviço localmente para a comunicação entre o spider e o javascript, tornando a estrutura complexa demais. Por esse motivo resolvi tomar outra abordagem e ignorar o PR anterior.
Easysearch e GWT
O Easysearch é um sistema de gerenciamento de documentos oferecido pela DataEasy, feito em GWT. Uma das principais funcionalidades dele é uma interface entre o Solr e a aplicação, permitindo a busca de documentos no sistema. A interface oferecida para a busca de diários de São Luis é uma tela feita no Easysearch, usando GWT, para servir a visualização e download dos arquivos.
GWT é um framework desenvolvido pelo Google para criação de aplicações web parecidas com aplicações desktop, parecido com Java Swing. A vantagem do framework é poder criar essas aplicações apenas com código java, sem a necessidade de implementar nada em JavaScript ou em HTML. Ele torna isso possível transpilando o código java para javascript e usando um protocolo de comunicação próprio entre o servidor e o cliente.
O problema é que Java e JavaScript não são parecidos e para haver troca de comunicação entre eles é necessária uma padronização. Esse problema foi resolvido pelos desenvolvedores através de um protocolo de comunicação onde é passada a informação dos objetos java serializados e o framework faz a interpretação/deserializacao desses dados. Talvez seja importante notar aqui que tanto o cliente quanto o servidor conhece os objetos, com seus tipos e tamanhos, ignorando essas informações durante a transmissão dos dados.
O protocolo GWT-RPC, usado nessa comunicação, é razoavelmente descrito nesse documento, por isso não vou entrar em muitos detalhes sobre ele aqui.
Contudo, existem alguns detalhes específicos do Easysearch que devem ser entendidos para entender a busca de documentos. São eles:
Fluxo de Busca
O Easysearch usa um endpoint especifico para a comunicação com o backend GWT, a URI easysearch/easysearch_searchview/gwtengine
. Para essa URL é possível enviar uma série de comandos diferentes. Para isso é chamado o método execute
da classe br.com.dataeasy.easysearch.client.core.ManagerService
. Esse método aceita um único parâmetro do tipo br.com.dataeasy.easysearch.client.core.ClientCommand
. Fazendo engenharia reversa no protocolo, descobri 3 classes que implementam esse tipo. São elas:
br.com.dataeasy.easysearch.client.command.LoadConfigCmd
: quando passado um objeto dessa classe para o método execute
, o sistema retorna um novo Cookie, criando uma sessão para o usuário. Acredito que ele seja responsável pela autorização do usuário.
br.com.dataeasy.easysearch.client.command.BootstrapCmd
: Carrega as informações do índice a ser buscado, no nosso caso o índice default
.
br.com.dataeasy.easysearch.client.command.SearchRequestCmd
: É o comando que faz a busca retornando os resultados com os diários.
Executando essas 3 chamadas na sequência foi possível imitar uma busca no site e ter as informações dos usuários. Nesse PR, foi imitada a busca para 1 dia especifico.
Tratando o retorno
Todo resultado com sucesso do protocolo GWT-RPC retorna os 4 primeiros bytes com a string //OK
. Apos esses 4 bytes vem um array com as informações da busca. Esse array pode ser dividido em 3 partes:
- índices/valores serializados — São os índices apontando para os tipos, apontando para os valores ou os valores em si, quando os tipos forem simples (com o número zero representando nulo).
- array com as strings que serão usadas nos objetos, os índices da parte anterior podem apontar para elas
- informações do protocolo (tipo e versão do protocolo)
Para reconstruir os objetos, é necessário saber exatamente quais as propriedades dentro da classe e a ordem delas. Como o código da aplicação é fechado, isso é impossível. Mas é possível usar uma heurística para extrair as informações dos diários repassadas pelo backend.
Partindo do princípio que todos os dados de classes iguais virão na mesma ordem, pode-se olhar os índices (primeira parte do array) e encontrar padrões. Para isso, o código encontra todos os valores do tipo java.lang.String
e adiciona eles a um vetor. Olhando o resultado, emerge um padrão, como o exemplo abaixo:
'default',
'pdf',
'bht.zip',
'↕×↕ɔ××api_Tamanho_do_arquivo_lg;××↕ɔ↕×api_ɉȏɓɨɗ_st;××↕ɔ××api_Localização_do_arquivo_tg;×××ƈ↕×api_Visualizar_bv;×××ɔ↕×api_Número_do_diário_tg;↕↕×ɔ↕×api_Data_do_diário_dt;×××ƈ××api_Download_bn;×↕×ɔ↕×api_Suplemento_tg',
'Sim',
'',
'193',
'Diário\\n \\nOficial\\n \\n \\n\\nDO MUNICÍPIO DE SÃO L...',
'abb27766ddeadeb04d7be46c3e90595924fff8f1',
'score',
'ctr_iid',
...
'ctr_timenow',
'pdf',
'bht.zip',
'ctr_doctype', '↕×↕ɔ××api_Tamanho_do_arquivo_lg;××↕ɔ↕×api_ɉȏɓɨɗ_st;××↕ɔ××api_Localização_do_arquivo_tg;×××ƈ↕×api_Visualizar_bv;×××ɔ↕×api_Número_do_diário_tg;↕↕×ɔ↕×api_Data_do_diário_dt;×××ƈ××api_Download_bn',
'',
'193',
'1\\nDO MUNICÍPIO DE SÃO LUÍS - MA\\n\\nDiário Oficial\\n\\nA...',
'132cc6bcbf1ec84641cab85c37cd9f9fd9832780',
Da análise dos links, eu sabia que essas strings com números em hexadecimal são os ids dos documentos (abb27766ddeadeb04d7be46c3e90595924fff8f1 e 132cc6bcbf1ec84641cab85c37cd9f9fd9832780). Logo acima delas, sempre vem um texto, que aparece como snippet do diário. E acima desse texto sempre vem um número (193*), representando o número do diário. Para complementar, antes do número do diário sempre vem uma string vazia, mas depois disso existe uma quebra da regra.
Na primeira entrada, na linha anterior da linha vazia vem o texto "Sim"
, enquanto na outra entrada vem uma string gigante. Essa string gigante lista os campos do índice do Solr, separados por uns caracteres estranhos. Os 2 exemplos tem essa string parecida. Olhando pela interface, vi que a diferença entre esses 2 casos eh que no primeiro caso o diário é uma edição extra (vem na interface o texto Suplemento: Sim
) e no segundo não é. Olhando a string com os campos, percebe-se também que no primeiro caso existe o campo api_Suplemento_tg
, enquanto o segundo não tem.
Com isso criei um algoritmo para extrair as informações da edição apenas olhando essas ‘strings’. Começa achando a string com os campos, depois vê se o próximo campo eh a string "Sim"
. Se for, marca a edição como extra e vai para os próximos campos, ignorando a linha em branco. Sobra então o número do diário e o ID, necessário para baixar o PDF.
O único problema que ainda não foi resolvido foi pegar a data do diário, não esta claro como ela é codificada no protocolo. Para resolver isso, a data buscada foi passada como metadados do Request
e atribuída nas edições retornadas.
fixes #184