Como fazer web crawling e scrapping com PHP
Você provavelmente já viu vários posts sobre como escrever crawlers com php. O que difere este post dos outros? É que eu garanto que você não precisa se malucar com expressões regulares, variáveis globais e todo esse tipo de coisa irritante.
Nós vamos usar uma ferramenta maravilhosa chamada spatie/crawler
que vai nos fornecer uma ótima interface para escrever crawlers sem ir a loucura!
Abaixo tem um vídeo meu codificando este crawler. É só rolar a página até o vídeo se tu quiser pular direto pra ação. 😉
Nosso caso de uso
Este crawler vai ser bem simplão e pretende buscar nomes, apelidos e e-mails do diretório oficial do PHP sobre pessoas que contribuíram com a linguagem de alguma forma.
Você pode olhar o repositório nesta url aqui: https://people.php.net.
Configurando o ambiente de desenvolvimento
Montar o ambiente vai ser bem rápido, eu vou só copiar as secções composer e php desse outro post que eu escrevi sobre como montar um ambiente com docker rapidex.
Meu arquivo docker-compose.yml ficou assim:
version: '3'
services:
composer:
image: composer:1.9.3
environment:
- COMPOSER_CACHE_DIR=/app/.cache/composer
volumes:
- .:/app
restart: never
php:
image: php:7.4-cli
restart: never
volumes:
- .:/app
working_dir: /app
Agora vamos instalar os pacotes:
$ docker-compose run \
composer require \
spatie/crawler \
symfony/css-selector
Tudo o que a gente precisa agora é um arquivo pra executar, vamos criar um arquivo bin/crawler.php
:
$ mkdir bin
$ touch bin/crawler.php
Massa! Agora vamos adicionar o autoload nesse arquivo e estamos prontos pra começar:
// bin/crawler.php
<?php
require_once __DIR__ .
'/../vendor/autoload.php';
De agora em diante a gente pode rodar nosso crawler com o seguinte comando:
$ docker-compose run php \
php bin/crawler.php
Vamos analizar o site alvo
Normalmente a gente deveria navegar pelo website e entender como ele funciona: padrões de url, chamadas ajax, tokens csrf, se feeds ou APIs estão disponíveis.
Neste caso nem feeds e nem APIs estão disponíveis. A gente precisa criar um crawler cruzão mesmo que vai buscar páginas em HTML e interpretá-las.
Eu vejo alguns padrões de URL: * Página de perfil: people.php.net/{nickname} * Página de diretório: people.php.net/?page={number} * Links externos
Parece simples! A gente só precisa se preocupar em interpretar o HTML dentro de páginas de perfil e ignorar o restante.
Ao verificar a página de perfil podemos perceber rapidamente que os seletores importantes pra gente são: * Nome: h1[property=foaf:name]
* Apelido: h1[property=foaf:nick]
A gente também pode confiar que o e-mail das pessoas segue o padrão "{apelido}@php.net".
Com essa informação, bora codar!
Obtendo dados públicos de todas as pessoas que contribuíram com o PHP
Abaixo você encontra o código, mas se você prefere mais vídeos, dá uma ligadinha nesse aqui que eu fiz pra ti:
Mãos à massa!
O pacote spatie/crawler
traz duas classes abstratas muito importantes - que eu adoraria que fossem interfaces 👀.
Uma delas é a classe CrawlObserver
, onde a gente pode se conectar aos passos de obter uma página e manipular respostas http. A nossa lógica entra aqui.
Eu vou escrever um observer rapidinho com uma classe anônima abaixo:
$observer = new class
extends CrawlObserver
{
public function crawled(
$url,
$response,
$foundOnurl
) {
$domCrawler = new DomCrawler(
(string) $response->getBody()
);
$name = $domCrawler
->filter('h1[property="foaf:name"]')
->first()
->text();
$nick = $domCrawler
->filter('h2[property="foaf:nick"]')
->first()
->text();
$email = "{$nick}@php.net";
echo "[{$email}] {$name} - {$nick}" . PHP_EOL;
}
};
A lógica acima vai buscar as propriedades que esperamos das páginas de perfil. É claro que a gente deveria também verificar se estamos na página correta ou não.
Agora, o próximo passo importante é a classe abstrata CrawlProfile
. Com esta classe a gente consegue decidir se uma URL deveria ou não ser acessada por um observer. Vamos criar também como classe anônima:
$profile = new class
extends CrawlProfile
{
public function shouldCrawl(
$url
): bool {
return $url->getHost() ===
'people.php.net';
}
};
Acima a gente definiu que queremos seguir apenas links internos. Isso porque esse website cria links pra vários outros repositórios. E a gente não quer crawlear todo o universo php, certo?
Com essas duas instâncias em mãos, podemos já preparar o crawler e iniciar a busca:
Crawler::create()
->setCrawlObserver($observer)
->setCrawlProfile($profile)
->setDelayBetweenRequests(500)
->startCrawling(
'https://people.php.net/'
);
Importante! Reparou naquele setDelayBetweenRequests(500)
? Ele faz com que o crawler vá buscar apenas uma URL a cada 500 milisegundos. Isso é porque a gente não quer derrubar esse site, certo? (Sérião, não derruba esse site. Se tu quer fazer maldade, busca um site do governo ou coisa do gênero 👀)
E é isso!
Rápido e prático, e mais importante de tudo: sem loucuras! O spatie/crawler
tem uma interface muito massa que simplifica demais o processo.
Se você juntar essa ferramenta com uma injeção de dependências e enfileiramento você terá resultados profissionais.
Me dá um toque no twitter se você tiver dúvidas! Uma abraço! 👋
Comentários