segunda-feira, 4 de outubro de 2010

Criando Spiders e Tranformando em Webservices com ZendFramework

Nem só de MVC vive o Zend Framework. Neste artigo, irá se mostrar como utilizar o ZF para a criação de um Spider e transformação do mesmo em um Web Service.

O que são spiders? A grosso modo, são robôs construídos com o objetivo de simular o comportamento de um navegador. Portanto, através de um spider pode-se realizar requisições HTTP de forma automatizada. A idéia é criar um spider e disponibilizá-lo através de um WebService. Para quem não sabe, WebService é uma forma de comunicação entre sistemas. Ou seja, após a transformação do Spider em um WebService, o serviço poderá ser acessado por qualquer aplicação, independente da linguagem da mesma. 

Existem várias tecnologias para construção de WebServices; A tecnologia usada no exemplo será SOAP. O artigo não entrará em detalhes da tecnologia SOAP, mas citará as responsabilidades de forma simples e objetiva de cada componente pertencente à tecnologia para entendimento do artigo.

Resumidamente, um Spider é uma classe que realiza requisições HTTP, faz o parse do HTML gerado pela requisição, geralmente utilizando expressões regulares,  para recuperação das informações relevantes ao domínio do problema e  faz a estruturação  dessas informações, retornando-as em objetos que representam o domínio do problema. Portanto, uma das características marcantes no desenvolvimento de um Spider é a possível quantidade de modificações necessárias ao longo do tempo, justamente por depender do HTML gerado pelo site. Caso o site mude o HTML de retorno, há a necessidade de mudança no Spider, seja na forma como as requisições sejam feitas ou na forma como os resultados são parseados.

Enfim, a idéia é seguir o conjunto de passos abaixo. A construção e transformação de um Spider em WebService segue basicamente este fluxo. 

1 - Definir serviço de um site e analisar o fluxo de entrada e saída. 

O serviço a ser usado neste artigo será do site do Tribunal Superior Eleitoral, mais especificamente o serviço do link <http://www.tse.gov.br/internet/servicos_eleitor/consultaSituacaoNome.htm>.

Este serviço do TSE tem como objetivo exibir a situação cadastral de um eleitor, bem como exibir seu título também. Para isso é necessário entrar com o NOME COMPLETO e DATA DE NASCIMENTO. Caso encontre o cidadão, como saída se tem o TITULO DE ELEITOR, NOME DO ELEITOR, DATA DE NASCIMENTO e SITUAÇÃO DO TÍTULO. Caso não encontre o cidadão, uma mensagem de erro é exibida pela aplicação.

As telas abaixo descrevem os casos acima.





2 - Modelar domínio do problema.

Após a análise do site, definindo entradas e saída, há a necessidade de se definir as classes que irão representar a modelagem do serviço.

Inicialmente, como já foi dito, sabe-se que o Spider é representado por uma classe. Por convenção, irá se usar o nome 'EleitorSpider'. Já se percebe também um método desta classe, que é justamente o método responsável por fazer a busca das informações. O nome do método será 'consultaSituacao'. Como entrada o método receberá um nome e uma data de nascimento. E o retorno do método será um objeto de uma outra classe que, por convenção, se chamará EleitorModel. Esta classe é responsável por armazenar as informações que serão retornadas pelo método 'consultaSituacao'. Ou seja: TITULO DE ELEITOR, NOME DO ELEITOR, DATA DE NASCIMENTO e SITUAÇÃO DO TÍTULO.

A imagem abaixo descreve a modelagem citada acima.
 


3 - Implementar a classe EleitorSpider.

Hora de implementar a classe EleitorSpider. Esta classe será responsável por encapsular todos os métodos para recuperação das informações. 

Para implementar essa classe, irá se usar o ZendFramework. Mais especificamente a classe Zend_Http_Client. Essa classe possui métodos para fazer requisições HTTP (GET e POST) bem como manipular o resultado destas requisições. Portanto, perfeito para implementação do Spider. Além disso, nesta etapa do processo de desenvolvimento é muito útil usar ferramentas que analisam a requisição HTTP, facilitando o trabalho do desenvolvedor. Essa facilidade diz respeito às variáveis que são passadas através das requisições, principalmente pelo método POST. Uma ferramenta muito útil é o HTTPFox. Ele funciona como um plugin do Firefox e permite a visualização das requisições HTTP e conteúdo gerado.

A imagem abaixo mostra com exatidão a requisição HTTP/POST feita pelo site para buscar as informações e que variáveis são passadas. Note que o HTTPFox funciona como um ícone bem no canto inferior direito do navegador e precisa ser ativado para visualização das requisições.


Analisando pelo HTTPFox, percebe-se o envio das variáveis necessárias para a consulta e a presença de um token. Este token é gerado pela página principal (a que carrega o formulário). Em geral, ao se criar spiders, pode-se enviar os dados via POST diretamente para a página de destino. Porém alguns sites implementam mecanismos para evitar a construção de spiders. Nesse caso, há a necessidade de seguir o fluxo completo.

Portanto, o fluxo da implementação já deve ficar definido na cabeça do programador, antes mesmo de começar a implementação. Este fluxo é:

a) Carregar página do formulário e recuperar o token a ser enviado posteriormente
b) Enviar os dados via POST (Token + Dados de pesquisa)
c) Parsear os resultados, montando objeto de saída (EleitorModel).

A classe EleitorModel é bem simples e a implementação dela é mostrada abaixo:




Note o uso dos comentários que documentam os atributos. São necessários e obrigatórios para facilitar a construção de serviços mais adiante. Portanto, devem ser preenchidos.

Abaixo a implementação da classe EleitorSpider, seguindo estes fluxos.



A classe EleitorSpider possui um atributo privado (client) que representa o objeto da classe Zend_Http_Client. Ela é instanciada e criada no construtor da classe.

Na modelagem, definimos um método chamado 'consultaSituacao'. Percebe-se que ele é o único método público da classe. E para implementar o método, outros métodos, privados, foram criados. Ou seja, são métodos que são usados apenas por outros métodos da classe, neste caso, 'consultaSituacao' apenas.

Dividir para conquistar sempre é uma boa prática de programação. Por isso, a implementação do método 'consultaSituação é bem simples e se utiliza destes métodos privados (step1, step1Parse, step2, step2Parse) para completar a tarefa. Olhando para o fluxo mencionado acima e analisando os métodos percebe-se a seguinte situação:

- Passo 1 (step1 e step1Parse)

O primeiro passo cobre o fluxo descrito no item (a) acima. Ou seja, ele carrega a página do formulário para recuperar o token que é enviado via POST. Para tanto, é feita uma subdivisão de tarefas também. Um método para realizar a requisição (GET) do formulário (step1) e outro método para parsear o resultado procurando pelo token (step1Parse) através de uma expressão regular. Vale notar que a URL para acesso do formulário é "invisível" aos olhos de um usuário pelo navegador e é necessário o uso do HTTPFox para descobrir a mesma (experimente!). Isso acontece em alguns sites, para esconder a URL final de pesquisa. Mas não engana o HTTPFox que verifica todas as requisições e é uma ferramenta muito útil para construção de Spiders.

- Passo 2 (step2 e step2Parse)

O segundo passo cobre o fluxo descrito nos itens (b) e (c). E segue a mesma lógica do passo 1. A idéia sempre é essa. Um método para realizar a requisição e um método para parsear o HTML gerado pela requisição, devolvendo a estrutura de dados correspondente. Nesta etapa, é realizada a requisição POST, enviando os dados necessários para a consulta, bem como o token recuperado do passo 1. O método step2parse realiza o parse do resultado. Neste passo já é possível recuperar todas as informações de retorno, portanto o objeto EleitorModel é montado e retornado neste passo. Mas antes de tentar recuperar o resultado encontrado, cria-se uma expressão regular para encontrar a frase da não existência de um resultado válido. Não existindo, uma exceção é lançada. Após esta verificação é que se tenta realmente buscar os dados corretos. Isso garante que tenhamos um resultado coerente ao nosso modelo. Uma outra observação importante é com relação a codificação da página. Como a classe irá se transformar em WebService mais para frente e é de bom costume que um Web Service retorne dados codificados em UTF-8, há a necessidade de se transformar os dados recuperados para UTF-8 quando o HTML da página for diferente de UTF-8. Neste caso, o site do TSE já está em UTF-8, não havendo necessidade dessa conversão. Mas caso fosse, o uso da função 'utf8_encode' do PHP seria necessária.

Por fim, nosso Spider está criado e montado. Para aprofundar o conhecimento nas funções do Zend_Http_Client, o manual é uma boa fonte de referência.

4 - Testar o Spider.

O código abaixo testa o Spider criado. Ele pode ser executado tanto em linha de comando quanto pelo navegador. O arquivo criado tem o nome de 'testSpider.php'. Neste artigo, considera-se que todos os arquivos estejam dentro do mesmo diretório.



- Para executar via linha de comando:

php testSpider.php 'edson arantes do nascimento' '23/10/1940'

- Para testar pelo navegador:

http://localhost/artigos/spider_to_webservice/testSpider.php?nome=edson arantes do nascimento&data=23/10/1940

Os valores mencionados acima devolvem resultados coerentes. Use outros valores para testar as outras condições, como por exemplo, um nome/data inválidos.

O resultado final imprime na tela os valores do objeto EleitorModel do resultado pesquisado ou a mensagem de exceção lançada.

5 - Criando o WebService

Hora de criar a classe que funcionará como serviço. O código abaixo contém a implementação da classe.



Alguns comentários pertinentes à implementação da classe 'EleitorService':

- Implementa basicamente o mesmo método existente no 'EleitorSpider' (consultaSituacao), com os mesmos argumentos.
- Note o uso dos comentários em formato de documentação. São úteis para geração do WSDL do serviço através da classe 'Zend_Soap_AutoDiscover'.
- O construtor da classe 'Zend_Soap_AutoDiscover' mapeia que tipo de estratégia é utilizada para mapeamento. Neste exemplo, utiliza a estratégia que permite até vetores de objeto como resultado. Maiores detalhes na documentação da classe. Além disso, ele usa os comentários feitos para gerar o WSDL, daí a importância da documentação nas classes EleitorService e EleitorModel.
- O WSDL nada mais é do que um 'contrato' que descreve que operações são existentes no serviço bem como a interface de entrada e saída deste serviço. Representa uma URL que servirá para o cliente SOAP que irá se criar posteriormente.
- Abaixo da implementação da classe, existem dois possíveis trechos de código. O primeiro, caso seja passada a string 'wsdl' na url de chamada do serviço, carrega o wsdl para leitura. Já o segundo caso, instancia uma classe 'Zend_Soap_Server' que carrega o WSDL e configura a classe 'EleitorService' como uma classe de serviço. Neste exato instante, um WebService é criado e pronto para ser chamado por um Soap Client de qualquer linguagem de programação.

6 - Testando o WebService

Semelhante ao Spider, o WebService também tem um arquivo para teste, que neste artigo se chama 'testService.php'. Abaixo a implementação do mesmo:



Note que neste arquivo, não se faz uso da classe Spider. Não há 'includes' para a mesma. Apenas para a classe 'EleitorModel' que é justamente a classe de retorno do serviço. Os dados são recebidos via URL ou linha de comando, da mesma forma como no arquivo de teste do Spider.

Uma instância da classe Zend_Soap_Client é criada, passando como argumento a URL do WSDL e um vetor de opções como segundo argumento. Neste vetor, é feita a associação entre a estrutura de dados SOAP criada pelo serviço e a classe que a representará. No caso ambas têm o nome de 'EleitorModel' como padrão.

Depois, uma chamada é feita ao método do serviço 'consultaSituacao'. O grande lance é que este script de teste do serviço pode estar em outro servidor web, de uma outra rede. Aí percebe-se a chamada do método remoto através do contrato existente (WSDL). Ou seja, qualquer aplicação que consiga visualizar a URL do WSDL tem acesso ao método do serviço criado.

Agora é só testar o serviço e partir para o abraço!

---------------------------

Finalizando, algumas considerações devem ser feitas, ao se tratar de Spiders:

- Muitos sites protegem seus dados finais através de um CAPTCHA, que são aquelas imagens com letras e números que aparecem para o usuário preencher.
- Há mecanismos de quebra de um captcha, ou seja, robôs que conseguem ler o conteúdo de imagens. Geralmente utilizam técnicas de segmentação de imagens misturadas com técnicas de inteligência artificial.
- De acordo com o grau de mudança de um site, há sempre a necessidade de manutenção do Spider. Mudança de HTMLs, de URLs, de variáveis implicam em mudança no Spider também. O próprio exemplo deste artigo pode não funcionar após certo tempo. O que importa é que uma exceção será lançada caso isso ocorra.
- Os exemplos deste artigo foram desenvolvidos dentro da seguinte estrutura, a partir de 'localhost':
    - artigos/spider_to_webservice/
        - EleitorModel.php
        - EleitorSpider.php
        - testSpider.php
        - EleitorService.php
        - testService.php
    - artigos/ZendFramework-1.10.8
- Portanto, caso mude a estrutura, atualize as URLs dos arquivos do artigo para a estrutura que corresponda a sua. Note a existência do ZendFramework na estrutura acima.
- Os arquivos deste artigo estão disponíveis para download neste link. Já estão inclusos os arquivos do ZendFramework da versão 1.10.8 no arquivo ZIP acima.
- Caso tenha dúvidas em relação há algum trecho de código acima (métodos de classes de Zend, expressões regulares, etc) basta postar um comentário no post deste artigo com a dúvida. Será um prazer discutí-la.

É isso aí. Bom desenvolvimento de Spider a todos!

0 comentários: