Como fazer feeds de sites dos outros com PHP
De vez em quando recebo emails de pessoas querendo aprender como fazer pra criar feeds de sites dos outros, assim como eu já fiz de diversos sites que, pelo menos na época, ainda não disponibilizavam nenhum tipo de feed.
A dificuldade para se criar feeds de sites dos outros é inversamente proporcional à qualidade do código (X)HTML do site em questão. O problema é que, em geral, os sites que têm um código mais arrumadinho, com algum cuidado tanto com sintaxe quanto com semântica, muito provavelmente já disponibilizam seus próprios feeds.
Portanto, o que nos sobra são aqueles sites que têm algum conteúdo que nos interessa, mas cujo desenvolvedor, além de não saber ainda o que é um feed, também não faz idéia de como escrever decentemente um documento HTML.
O trabalho consiste basicamente de cinco passos:
- Pegar o código do site (via HTTP)
- Analizar o código e identificar onde está o conteúdo que nos interessa:
- Título
- Data de publicação
- Autor
- Link
- Sumário e/ou conteúdo
- Criar expressões regulares para isolar cada um desses dados
- Preparar os dados para inserção no feed
- Iterar pelos dados e criar, enfim, o código do feed
Com certeza, os passos mais complicados são o passo 2 e o passo 3. Para eles não há receita de bolo. Cada caso é um caso e alguns casos podem te tirar algumas noites de sono pra resolver.
Vamos passo a passo. Vamos usar um site fictício, sitesemfeed.com:
Comece seu código colocando em uma variável a URL onde se encontra o conteúdo que você deseja transformar em feed:
<?php
$url = "http://semfeed.com/arquivo.html";
Pegar o código do site via HTTP
Esse passo é simples. Há duas maneiras. Uma usando file() (ou file_get_contents()), que só vai funcionar caso seu provedor de hospedagem tenha allow_url_fopen habilitado o que não é muito seguro e a maioria das hospedagens, incluindo a dreamhost, deixam desabilitado, e a outra usando CURL.
Para a primeira opção o código é simples:
Se a sua versão do PHP for menor que 4.3.0, use o seguinte:
$content = file($url);
$content = implode('', $content);
Caso contrário:
$content = file_get_contents($url);
Nesse ponto, o código HTML inteiro retornado pela URL do nosso site está na variável $content.
Pode ser interessante, em alguns casos, remover as quebras de linha do código. Faça assim:
$content = str_replace("\r", "", $content);
$content = str_replace("\n", "", $content);
Se você precisar usar o CURL, aqui está uma funçãozinha simples, tirada do próprio site do PHP:
function open_url($url){
$curl = curl_init();
curl_setopt ($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$content = curl_exec ($curl);
curl_close ($curl);
return $content;
}
Use assim:
$content = open_url($url);
Em qualquer um dos casos, a não ser que o site em questão já esteja em UTF-8, faça:
$content = utf8_encode($content); //se o site for em ISO-8859-1
$content = iconv('windows-1252', 'UTF-8', $content); //caso seja windows-1252
Tente sempre com o primeiro exemplo. Se der algum problema, tente o segundo. Sites em windows-1252 frequentemente aparecem como ISO-8859-1 para os browsers. Mais sobre encodings e a diferença entre ISO-8859-1 e windows-1252.
Analizar o código e identificar onde está o conteúdo que nos interessa
Essa parte vai ser tão difícil quanto mais bizarro for o código do site. Para simplificar o artigo, vamos imaginar que o site tenha apenas título, data, link e um sumário.
Este é o código que marca os títulos:
<tr><td><font face="Verdana" size="3" color="#000FF"><a HREF="001.html">Aqui está o título!</a></td></tr>
Claro, agradeça aos céus se o código que você for trabalhar for tão simples assim, com tags de fechamento, por exemplo. Frequentemente encontramos coisas muito mais bizarras por aí.
Enfim, o que podemos usar como informação única para identificar o título? Talvez a cor da fonte, talvez o tamanho. Você vai precisar olhar o restante do código pra saber o que tem de único nessa marcação. Claro, uma classe já ajudaria muito, mas não conte com isso.
Vamos usar a cor e o tamanho da fonte como nossos "flags" para os títulos.
De quebra temos o link nessa mesma linha. Sorte nossa.
Vamos ao código das datas:
<td><font size="1">11 de novembro de 2006</font></td>
Aqui teríamos um problema. Várias outras informações no site estão em um FONT com tamanho 1 dentro de um TD. Vamos tentar usar o formato da data para identificar as datas para o feed.
Agora o código dos sumários:
<table border=0 cellspacing="0"><tr><font size="2" color="#000000">Este é o sumário do <a href="teste.html">texto</a><br><b>cheio de marcação estranha <i></b>etc e tal<br><br>E por aí vai...</font><td></td></tr></table>
Mais uma vez, temos em diversos outros pontos do site, a mesma fonte, dentro de um TD, etc. Mas, por sorte, podemos usar a tabela com um único TR e um único TD como identificador para nossos sumários.
Claro que, como já disse, não é fácil reproduzir as bizarrices que costumam fazer por aí. Portanto, pode ser que você encontre casos muito mais complicados, quase impossíveis. Mas, nesses casos, seu sucesso vai depender de sua perspicácia. Novamente, não tem receita de bolo…
Criar expressões regulares para isolar cada um desses dados
Para os títulos e links:
preg_match_all("/<tr><td><font face=\".+\" size=\"3\" color=\"#000FF\"><a [Hh][Rr][Ee][Ff]=\"([^\"]+)\">(.+)<\/a><\/td><\/tr>/", $content, $tit_links);
A variável $tit_links é um array contendo os resultados da busca usando a expressão regular. O índice 0 contém a string completa, 1 contém o conteúdo do primeiro parêntese, o 2 o do segundo parêntese e por aí vai.
Não vou explicar aqui como funcionam expressões regulares. Se você não conhece o assunto, vale muito a pena aprender. Há um guia excelente na web e um livro, escritos pelo mesmo autor, Aurélio Marinho Jargas.
Vamos à ER para as datas:
preg_match_all("/<td><font size=\"[0-9]\">([0-9][0-9]? de [A-Za-z]+ de [0-9][0-9][0-9][0-9])<\/font><\/td>/", $content, $dates);
Há meios de se usar expressões mais complexas e menos extensas para este mesmo caso, mas procurei simplificar para tentar facilitar o entendimento.
Agora os sumários:
preg_match_all("/<table border=\"?0\"? cellspacing=\"[0-9]\"><tr><td><font size=\"[0-9]\" color=\"#[0-9]+\">(.+)</font><td><\/td><\/tr><\/table>/", $content, $summaries);
Estou levando em conta que podem, no futuro, resolver mudar o tamanho da fonte, ou colocar aspas nos atributos que não as têm.
Perceba que estou usando um caso fictício, não testado, apenas para exemplificar. Se você não analizar por si mesmo o site e não entender como as expressões regulares funcionam, é melhor nem tentar fazer um feed.
Mas, eu não poderia usar um parser HTML? Talvez sim. Eu já fiz alguns testes e achei mais fácil usar expressões regulares. Se você teve alguma experiência boa usando um parser HTML em PHP, conte pra gente.
Se o cara que fez o site mudar o código, meu feed já era? Yep. Esse é um grande problema de feeds feitos dessa forma. Se o cidadão mudar o código seu feed pára de funcionar. Simples assim.
E quanto ao copyright do site do camarada? Bem, tome cuidado com isso. É melhor consultar o dono do site antes, ou então assumir os riscos, se você tiver cara pra isso…
Criando o feed
Nesse ponto vamos iterar pelos itens coletados com as expressões regulares e criar o código do feed. Vou usar o formato RSS 2.0, que é mais popular.
O passo "preparar os dados para inserção no feed" vai ser feito dentro da iteração.
<?php
header("Content-type: application/xml; charset=utf-8"); //Sem isso, o arquivo será enviado como HTML
echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
?>
<rss version="2.0">
<channel>
<title>Site Sem Feed</title>
<link><?php echo $url ?></link>
?>
for ($i=0; $i<sizeof($tit_links); $i++): //considerando que todos os arrays tem o mesmo número de índices, não importa muito o tamanho de qual você pega aqui
$title = $tit_links[2][$i];
$link = $tit_links[1][$i];
$tz_offset = -3;
$date = date("D, d M Y H:i:s \G\M\T", strtotime($dates[1][$i]) + ($tz_offset * 3600)); //Isso aqui não vai funcionar, na verdade. Você vai ter que queimar a mufa pra transformar aquela data do exemplo em algo usável. Tô sem saco pra isso agora…
$summary = $summaries[1][$i];
$summary = strip_tags($summary, '<a> <br>
<p>');
?>
<item>
<title><?php echo $title ?></title>
<link><?php echo $link ?></link>
<pubDate><?php echo $date ?></pubDate>
<description><![CDATA[ <?php echo $summary ?> ]]></description>
</item>
<?phpphp endfor; ?>
</channel>
</rss>
E assim está feito nosso feed.
Antes de ter sucesso, você vai provavelmente ter que fazer diversos testes, arrancar alguns cabelos, xingar a mãe do cara que fez aquele site tão bizarro e tudo mais. Mas, quem quis fazer o feed foi você, então agüenta, malandro.


Uma sugestão seria usar as funções DOM do PHP.
Por que as vezes fica difícil isolar o conteúdo usando apenas expressões regulares, aconteceu comigo qndo fui tentar fazer isso para criar feeds para fotologs hospedados no flogao.com.br.