Se você precisar analisar HTML, as expressões regulares não são o caminho a seguir. Neste tutorial, você aprenderá como usar um analisador de software livre facilmente aprendido para ler, modificar e devolver HTML de fontes externas. Usando o Envato Tuts+ como exemplo, você aprenderá como obter uma lista de todos os artigos publicados no site e exibi-los.
1. Preparação
A primeira coisa a fazer é instalar o Composer, que permite instalar facilmente pacotes PHP.
Em seguida, crie uma nova pasta, abra um terminal dentro dessa pasta e execute composer init
no terminal. Em seguida, percorra a caixa de diálogo com respostas como esta:
As duas partes importantes são selecionar project
como o tipo e adicione voku/simple_html_dom
como uma dependência. Você pode configurar o resto como quiser.
Finalmente, crie um arquivo no diretório chamado simple_html_dom.php e coloque o seguinte no arquivo:
1 |
|
2 |
|
3 |
use vokuhelperHtmlDomParser; |
4 |
|
5 |
require_once 'composer/autoload.php'; |
6 |
2. Parsing Basics
This library is very easy to use, but there are some basics you should review before putting it into action.
Loading HTML
1 |
// Load from a string
|
2 |
$dom = HtmlDomParser::str_get_html(' |
3 |
// Load from a file or URL
|
4 |
$dom = HtmlDomParser::file_get_html('https://bbc.com'); |
Você pode criar seu objeto inicial carregando HTML de uma string ou de um arquivo. O carregamento de um arquivo pode ser feito via URL ou por meio do sistema de arquivos local.
Uma nota de cautela: o load_file()
método delega seu trabalho para PHP’s file_get_contents
. Se allow_url_fopen
não estiver definido como true em seu arquivo php.ini, talvez você não consiga abrir um arquivo remoto dessa maneira. Você sempre pode recorrer à biblioteca cURL para carregar páginas remotas nesse caso.
Acessando Informações
Depois de ter seu objeto DOM, você pode começar a trabalhar com ele usando find()
e criação de coleções. Uma coleção é um grupo de objetos encontrados por meio de um seletor — a sintaxe é bastante semelhante à jQuery.
1 |
|
2 |
|
3 |
Hello World!
|
4 |
We're Here.
|
5 |
|
6 |
|
Neste exemplo de HTML, vamos dar uma olhada em como acessar as informações no segundo parágrafo, alterá-las e, em seguida, exibir os resultados.
1 |
// create & load the HTML
|
2 |
$dom = HtmlDomParser::str_get_html(" |
3 |
// get all paragraph elements
|
4 |
$element = $dom->find("p"); |
5 |
// modify the second
|
6 |
$element[1]->innerText .= " and we're here to stay."; |
7 |
// print it
|
8 |
echo $dom->save(); |
Usar o método find() sempre retorna uma coleção (array) de tags, a menos que você especifique que deseja apenas o enésimo filho, como segundo parâmetro.
Primeiro carregamos o HTML de uma string, conforme explicado anteriormente.
o find
chamada de método encontra tudo
tags no HTML e as retorna como uma matriz. O primeiro parágrafo terá um índice de 0 e os parágrafos subsequentes serão indexados de acordo.
Por fim, acessamos o segundo item de nossa coleção de parágrafos (índice 1) e fazemos uma adição ao seu innertext
atributo. innertext
representa o conteúdo entre as tags, enquanto outertext
representa o conteúdo, incluindo a tag. Poderíamos substituir a tag inteiramente usando outertext
.
Vamos adicionar mais uma linha e modificar a classe da nossa segunda tag de parágrafo.
1 |
$element[1]->class = "class_name"; |
2 |
echo $dom->save(); |
O HTML resultante do comando salvar seria:
1 |
|
2 |
|
3 |
Hello World!
|
4 |
|
5 |
|
6 |
|
Outros seletores
Aqui estão alguns outros exemplos de seletores. Se você já usou jQuery, isso parecerá muito familiar.
1 |
// get the first occurrence of id="foo"
|
2 |
$single = $dom->find('//foo', 0); |
3 |
// get all elements with class="foo"
|
4 |
$collection = $dom->find('.foo'); |
5 |
// get all the anchor tags on a page
|
6 |
$collection = $dom->find('a'); |
7 |
// get all anchor tags that are inside H1 tags
|
8 |
$collection = $dom->find('h1 a'); |
9 |
// get all img tags with a title of 'himom'
|
10 |
$collection = $dom->find('img[title=himom]'); |
O primeiro exemplo não é totalmente intuitivo — todas as consultas por padrão retornam coleções, até mesmo uma consulta de ID, que deve retornar apenas um único resultado. No entanto, ao especificar o segundo parâmetro, estamos dizendo “retorne apenas o primeiro item desta coleção”.
Isso significa $single
é um único elemento, em vez de uma matriz de elementos com um item.
O resto dos exemplos são auto-explicativos.
Documentação
A documentação completa sobre a biblioteca pode ser encontrada no GitHub oficial do projeto.
3. Um exemplo do mundo real
Para colocar esta biblioteca em ação, vamos escrever um script rápido para coletar o conteúdo do site Envato Tuts+ e produzir uma lista de artigos presentes no site por título e descrição… apenas como exemplo.
1 |
use vokuhelperHtmlDomParser; |
2 |
require_once 'vendor/autoload.php'; |
3 |
|
4 |
$articles = array(); |
5 |
getArticles('https://code.tutsplus.com/tutorials'); |
Começamos incluindo a biblioteca e chamando o getArticles
função com a página que gostaríamos de começar a analisar. Neste caso, estamos começando perto do fim e sendo gentis com o servidor do Tuts+.
Também estamos declarando uma matriz global para simplificar a coleta de todas as informações do artigo em um só lugar. Antes de começarmos a análise, vamos dar uma olhada em como o resumo de um artigo é descrito no Tuts+.
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
... |
8 |
|
9 |
class="posts__post-author">...
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
class="posts__post-primary-category-link topic-code" href="">...
|
15 |
|
16 |
|
17 |
|
Isso representa um formato básico de postagem no site, incluindo comentários do código-fonte. Por que os comentários são importantes? Eles contam como nós para o analisador.
4. Iniciando a função de análise
1 |
function getArticles($page) { |
2 |
global $articles; |
3 |
$html = HtmlDomParser::file_get_html($page); |
4 |
...
|
5 |
}
|
Começamos obtendo a variável global $articles
e carregar a página no Simple HTML DOM usando file_get_html
como fizemos anteriormente. $page
é a URL que passamos anteriormente
5. Encontrando as informações que queremos
1 |
$items = $html->find('article'); |
2 |
foreach($items as $post) { |
3 |
$articles[] = array(/* get title */ $post->findOne(".posts__post-title")->firstChild()->text(), |
4 |
/* get description */ $post->findOne("posts__post-teaser")->text()); |
5 |
}
|
Esta é a carne do getArticles
função. Vai dar uma olhada mais de perto para realmente entender o que está acontecendo.
Primeiro criamos um array de elementos—div’s com a classe de preview
. Agora temos uma coleção de artigos armazenados em $items
.
No bloco foreach, $post
agora se refere a um único div da classe preview
. Se olharmos para o HTML original, podemos ver que o título da postagem está contido no primeiro filho de posts__post-title
. Portanto, para obter o título, pegamos o texto desse elemento.
A descrição está contida em posts_post-title
. Tiramos o texto desse elemento e colocamos o segundo elemento no article
item. Um único registro em artigos agora se parece com isto:
1 |
$articles[0][0] = "My Article Name Here"; |
2 |
$articles[0][1] = "This is my article description" |
6. Paginação
A primeira coisa que fazemos é determinar como encontrar nossa próxima página. No Tuts+, as URLs são fáceis de descobrir, mas vamos fingir que não são e obter o próximo link por meio de análise.
Se olharmos para o HTML, veremos o seguinte:
Se houver uma próxima página (e nem sempre haverá), encontraremos uma âncora com a classe de nextpostslink
. Agora essa informação pode ser colocada em uso.
1 |
if($next = $html->find('a[class=pagination__next-button]', 0)) { |
2 |
$URL = $next->href; |
3 |
$html->clear(); |
4 |
unset($html); |
5 |
getArticles($URL); |
6 |
}
|
Na primeira linha, vemos se conseguimos encontrar uma âncora com a classe pagination__next-button
. Preste atenção especial ao segundo parâmetro para find()
. Isso especifica que queremos apenas o primeiro elemento (índice 0) da coleção encontrada retornado. $next
conterá apenas um único elemento, em vez de um grupo de elementos.
Em seguida, atribuímos o href do link à variável $URL
. Isso é importante porque estamos prestes a destruir o objeto HTML. Devido a um vazamento de memória de referências circulares do PHP, $html
deve ser limpo e desativado antes que outro seja criado. Não fazer isso pode fazer com que você consuma toda a memória disponível.
Finalmente, chamamos getArticles
com a URL da próxima página. Essa recursão termina quando não há mais páginas para analisar.
Você terminou a raspagem! É assim que o código final deve ficar:
1 |
|
2 |
use vokuhelperHtmlDomParser; |
3 |
require_once 'vendor/autoload.php'; |
4 |
|
5 |
$articles = array(); |
6 |
getArticles('https://code.tutsplus.com/tutorials'); |
7 |
|
8 |
function getArticles($page) { |
9 |
global $articles; |
10 |
$html = HtmlDomParser::file_get_html($page); |
11 |
$items = $html->find('article'); |
12 |
foreach($items as $post) { |
13 |
$articles[] = array(/* get title */ $post->findOne(".posts__post-title")->firstChild()->text(), |
14 |
/* get description */ $post->findOne("posts__post-teaser")->text()); |
15 |
}
|
16 |
if($next = $html->find('a[class=pagination__next-button]', 0)) { |
17 |
$URL = $next->href; |
18 |
$html->clear(); |
19 |
unset($html); |
20 |
getArticles($URL); |
21 |
}
|
22 |
}
|
7. Saída dos resultados
Primeiro, vamos configurar alguns estilos básicos. Isso é completamente arbitrário←você pode fazer com que sua saída tenha a aparência que desejar.
1 |
#main { |
2 |
margin:80px auto; |
3 |
width:500px; |
4 |
}
|
5 |
h1 { |
6 |
font:bold 40px/38px helvetica, verdana, sans-serif; |
7 |
margin:0; |
8 |
}
|
9 |
h1 a { |
10 |
color:#600; |
11 |
text-decoration:none; |
12 |
}
|
13 |
p { |
14 |
background: #ECECEC; |
15 |
font:10px/14px verdana, sans-serif; |
16 |
margin:8px 0 15px; |
17 |
border: 1px #CCC solid; |
18 |
padding: 15px; |
19 |
}
|
20 |
.item { |
21 |
padding:10px; |
22 |
}
|
Em seguida, vamos colocar um pouco de PHP na página para exibir as informações armazenadas anteriormente.
1 |
|
2 |
foreach($articles as $item) { |
3 |
echo " |
4 |
echo $item[0]; |
5 |
echo $item[1]; |
6 |
echo ""; |
7 |
}
|
8 |
?>
|
O resultado final é uma única página HTML listando todos os artigos, começando na página indicada pelo primeiro getArticles()
ligar.
Conclusão
Se você estiver analisando muitas páginas (digamos, o site inteiro), pode demorar mais do que o tempo máximo de execução permitido pelo seu servidor. Por exemplo, executar a partir da minha máquina local leva cerca de um segundo por página (incluindo o tempo de busca).
Em sites grandes como o Envato Tuts+, isso pode levar muito tempo.
Este tutorial deve ajudá-lo a começar a analisar HTML. Existem outros métodos para trabalhar com o DOM, incluindo o interno do PHP, que permite trabalhar com seletores xpath poderosos para localizar elementos. Mas, para facilidade de uso e início rápido, considero esta biblioteca uma das melhores.
Como nota final, lembre-se sempre de obter permissão antes de raspar um site: isso é importante. Obrigado por ler!