My Profile Photo

Jociel Souza

PHP Clojure Javascript Mongodb ZendFramework


Desenvolvedor Web - Backend - PHP


Estudante de Ciência da Computação, desenvolvedor de software apaixonado por tecnologia.


Estudante de Ciência da Computação procurando sempre aprender algo novo, desenvolvedor web com conhecimento em PHP7^, Zend Framework, Zend Expressive, Doctrine ORM, Doctrine ODM, MongoDB, Javascript, VueJS, HTML, GIT entre outros, atualmente estudando também Clojure, Elm e o paradigma funcional e tentando compartilhar o conhecimento adquirido.


Web Crawler básico com Dom Crawler Component do Symfony | Jociel Souza

Web Crawler básico com Dom Crawler Component do Symfony


Já trabalhei um tempo com um sistema onde era preciso criar web crawlers para capturar algumas informações de sites da web, recentemente comecei a brincar um pouco com um componente do framework Symfony chamado DomCrawler, este componente é basicamente um parser de HTML que facilita a manipulação do DOM de uma página web possibilitando a captura de dados da página de forma fácil e simples.

Nate Grant em Unsplash


DOM… Crawler… What the fu** is this?

De maneira simples vamos entender primeiramente estes conceitos.
O código HTML de uma página é formado por tags que formam uma estrutura de árvore com uma dentro de outras e assim por diante, o DOM (Document Object Model) fornece uma representação estruturada no formato de uma árvore do documento HTML para o browser contendo os nós, que seriam as tags html e cada nó possuindo suas propriedades.

Wikipedia


Um Crawler, Web Crawler ou também conhecido como Spider é um mecanismo automatizado que possui a capacidade de navegar entre sites e fazer a “leitura” destas páginas para captura de informações ou outras tarefas necessárias, é a base de muitas ferramentas de buscas hoje em dia como, por exemplo o Google, onde uma das tarefas é indexar os links dos sites em seu banco de dados. Existem diversos objetivos para criar um web crawler como para analisar os links e verificar se estão ativos ou não, para análise de dados, ou também para um trabalho da faculdade onde eu fiz a captura de informações para treinar um algoritmo de classificação de textos. :P


também é possível capturar informações de arquivos de textos, PDFs, etc.


DomCrawler

O DomCrawler é um componente do Framework Symfony que facilita a navegação no DOM de documentos HTML e XML, ele fornece alguns métodos para manipulação destes documentos e captura de informações, iremos utilizá-lo para criar nosso web crawler. Por trás dos panos este componente utiliza a classe DOMElement do PHP para criar os nós do DOM para podermos navegar entre eles.

Guzzle

Outra ferramenta que utilizaremos será o Guzzle, responsável por criar e manipular as requests e responses para links que queremos capturar as informações.


Let’s Code

Primeiramente dentro do diretório onde irá ficar nosso web crawler vamos iniciar o composer.

composer init

Irei apenas dar enter em todas as opções que o composer solicitar deixando todas com o valor default, após isto iremos instalar as ferramentas necessárias, o Guzzle, DomCrawler e Css Selector. O Css Selector utilizaremos junto com o DomCrawler.

composer require guzzlehttp/guzzle
composer require symfony/dom-crawler
composer require symfony/css-selector

Após a terminada a instalação vamos criar o arquivo do web crawler chamado webcrawler.php na raiz do projeto, não irei utilizar nenhuma estrutura mais complexa aqui, criarei tudo no mesmo arquivo visto que os métodos e conceitos podem ser aplicado posteriormente em uma aplicação mais complexa.
Primeiramente vamos fazer o require do autload do diretório vendor para termos acesso às classes das ferramentas que utilizaremos.

<?php

require_once "./vendor/autoload.php";

As informações que iremos capturar são as críticas e notas dadas por usuários em um determinado filme em um site sobre cinema, primeiramente precisaremos fazer uma requisição para o link para pegar a página em formato texto(html) com o Guzzle.

$client = new \GuzzleHttp\Client();

$response = $client->get("http://www.adorocinema.com/filmes/filme-141110/criticas/espectadores/");

$html = $response->getBody()->getContents();

var_dump($html);

Primeiro criamos um Http Client para podermos fazer requests para uma página web, após isso utilizamos este objeto para realizar uma requisição do tipo GET para o link que queremos, o método get do Client do Guzzle retorna uma ResponseInterface então podemos utilizar os métodos getBody e getContents para termos o html da página.
Podemos testar entrando no diretório do projeto e executando o seguinte comando:
php webcrawler.php ao final será apresentado o html da página.

Este html passamos na criação do Crawler, esta classe fornece métodos para manipulação do DOM html.

$crawler = new \Symfony\Component\DomCrawler\Crawler($html);


A partir desse ponto podemos utilizar os métodos do Symfony Crawler para percorrer os nós da página e capturar as informações necessárias.
Como comentei anteriormente, iremos capturar as críticas e a nota dada por usuários em um determinado filme, primeiro temos que analisar a página, então vamos acessar o link utilizado no método get do client http, vamos abrir a ferramenta de desenvolvedor do browser, navegando pelas tags podemos perceber que todas as informações que queremos estão dentro de várias divs com class review-card-review-holder

Então usaremos o método filter para filtrar apenas essas divs que nós queremos, o CSS Selector que instalamos permite utilizarmos uma sintaxe similar ao jQuery para acessar determinado nó do DOM, podemos filtrar por qualquer atributo como classe, id, name ou qualquer outro.

$contentContainer = $crawler->filter('div[class="review-card-review-holder"]');

O método filter retorna um novo objeto Crawler contendo apenas os elementos correspondentes aquela query que passamos no filter, o site faz paginação mostrando apenas 10 comentários por páginas, para verificarmos se pegamos as dez divs podemos usar o método count.

var_dump($contentContainer->count()) ; 10


Analisando novamente a estrutura da página vemos que a nota dada pelos usuários estão em tags span com class stareval-note dentro das divs que já filtramos e o texto da crítica está em divs com class content-txt review-card-content.

Primeiro vamos capturar essas informações de uma maneira não muito legal para entendermos o que esta acontecendo, primeiramente vamos capturar as informações das notas.

$rates = $contentContainer->filter('span[class="stareval-note"]');

Assim podemos percorrer como um array e pegar as informações, aqui irei apenas imprimir na tela, mas poderia ser executado alguma funcionalidade para salvar no banco, processar essa informação ou qualquer outra ação.

foreach ($rates as $rate) {
    var_dump($rate->textContent);
}

Da mesma forma ocorre para o texto da crítica.

$texts = $contentContainer->filter('div[class="content-txt review-card-content"]');
foreach ($texts as $text) {
    var_dump($text->textContent);
}

Dentro do foreach estamos manipulando objetos da classe DOMElement então utilizamos a propriedade textContent para pegarmos a informação necessária.
Como podemos ver, as informações estão separadas e não ficou muito bom esse código, precisando executar um merge ou alguma outra forma para juntar o texto com sua respectiva nota, então vamos refatorar utilizando alguns métodos do Crawler do Symfony.


Como vimos o método filter retorna um novo Crawler e esta classe contém o método each similar a um map, simplificando o conceito do método each é que ele permite executar uma determinada função em cada item de um conjunto retornando um novo conjunto com os elementos após a aplicação da função.

$data = $crawler->filter('div[class="review-card-review-holder"]')->each(function ($contentContainer) {
    /** @var \Symfony\Component\DomCrawler\Crawler $contentContainer */
    $rate = $contentContainer->filter('span[class="stareval-note"]')->text();
    $text = $contentContainer->filter('div[class="content-txt review-card-content"]')->text();

    return [
        "rate" => $rate,
        "text" => $text
    ];
});

Após executar o filter para recuperarmos os nós que contém as informações que queremos, executamos o método each passando uma função que será executada em cada um desses nós, dentro dessa função executamos um filter novamente para pegarmos o nó onde temos a nota e o texto e após utilizamos o método text para pegarmos efetivamente o conteúdo que precisamos, assim retornamos um array contendo o texto e sua respectiva nota, no final temos um array $data com a seguinte estrutura.

[
    [
        "rate" => valor-nota
        "text" => texto
    ],
    [
        "rate" => valor-nota
        "text" => texto
    ],
    [
        "rate" => valor-nota
        "text" => texto
    ],
    ......
]

Assim temos uma estrutura fácil de manipular contendo as informações necessárias.


O código final ficou desta maneira.

<?php

require_once "./vendor/autoload.php";

$client = new \GuzzleHttp\Client();

$response = $client->get("http://www.adorocinema.com/filmes/filme-141110/criticas/espectadores/");

$html = $response->getBody()->getContents();


$crawler = new \Symfony\Component\DomCrawler\Crawler($html);


$data = $crawler->filter('div[class="review-card-review-holder"]')->each(function ($contentContainer) {
    /** @var \Symfony\Component\DomCrawler\Crawler $contentContainer */
    $rate = $contentContainer->filter('span[class="stareval-note"]')->text();
    $text = $contentContainer->filter('div[class="content-txt review-card-content"]')->text();

    return [
        "rate" => $rate,
        "text" => $text
    ];
});


var_dump($data);


O componente DomCrawler do Symfony também contém classes especiais para captura e manipulação de links, imagens e formulários, além de permitir algumas querys mais complexas para percorrer o DOM html com Expression Evaluation, também permite capturar informações sobre os atributos das tags entre outras funcionalidades. É uma ferramenta que vale a pena dar uma olhada.


Referências:
  • https://symfony.com/doc/current/components/dom_crawler.html
  • https://developer.mozilla.org/pt-BR/docs/DOM/Referencia_do_DOM
  • https://linux.ime.usp.br/~cef/mac499-06/monografias/andre/WebCrawler.html
comments powered by Disqus