É fato que boa parte da repercussão do BitchMaps se deve ao tema inusitado. Mas a idéia era justamente mostrar que endereços físicos estão disponíveis nos lugares mais improváveis da web, e que não é preciso uma estrutura complexa para localizar, processar e exibir esta informação em forma de mapa.
O objetivo desta série (em três partes) é ajudar a colocar as técnicas utilizadas ao alcance de todos, permitindo a criação de ferramentas semelhantes para outras fontes de dados. Algum conhecimento de Ruby on Rails ajudará na compreensão dos trechos de código, mas não é essencial – basta entender um pouco de programação/web (e não ter medo de experimentar).
Parte 1 – Agregando informações de sites.
Antes de escrever qualquer tipo de agregador é recomendável fazer uma análise da fonte de dados. Leve em conta não apenas os aspectos técnicos (tais como formato da informação, organização do site, URLs e navegabilidade da fonte), mas também os legais, logísticos e éticos (copyright, impacto da captura no site, efeito sobre a audiência – jamais “roube” tráfego de um site, restrições no robots.txt, etc.)
No caso do BitchMaps, esta fonte de dados era o site GPGuia. Ele é dividido em fórums, que são sub-divididos em tópicos, e estes últimos formados por mensagens. Cada uma dessas entidades possui identificadores numéricos atribuídos pelo phpBB (software no qual o site se baseia).
Os fórums representam categorias, e, em alguns casos, cidades ou regiões – e esses são os que interessam. Dentro deles, cada tópico corresponde a um ponto que desejamos mapear. E o endereço geográfico, quando existe, está no título do tópico – mas não inclui cidade ou estado (pois isso já é indicado pelo fórum ao qual o tópico pertence).
Todos estes dados vieram de simples observação (até o momento da publicação deste post não tenho qualquer relação com o GPGuia ou seus responsáveis), e permitem observar que é possível agregar valor (mapas) sem prejudicar o site – bastava que os pontos do mapa dessem link de volta para os tópicos que os originaram.
Também fica fácil ver que a captura pode ser feita sem varrer o site todo (apenas as páginas de tópicos são necessárias), minimizando a carga sobre o site nas atualizações. O site tem dois robots.txt: o da raiz é apenas uma mensagem de manutenção e o outro é inválido (por estar no local incorreto). Mesmo assim vamos respeitar este último e não entrar em nenhum diretório apontado nele.
O primeiro passo, então, é mapear, dentre os tópicos que contém endereços relevantes, aqueles que possam ser identificados com uma cidade, associando os identificadores numéricos às cidades. Temos:
# Relaciona, para cada cada cidade(+estado), os fórums com endereços $cidade_forum = { "Sao Paulo - SP" => [28, 8, 9, 10, 36], "Campinas - SP" => [135], "Curitiba - PR" => [57], "Londrina - PR" => [229, 230], "Porto Alegre - RS" => [40, 195], "Rio de Janeiro - RJ" => [89, 212], "Belo Horizonte - MG" => [95] }
Com esta informação podemos analisar cada um dos fórums:
# Percorre a relação de cidades/fórums, atualizando o banco de dados def updatedb() $cidade_forum.keys.each do |cidade_estado| $cidade_forum[cidade_estado].each do |forum_id| scrape(forum_id, cidade_estado) end end end
A função scrape vai abrir a página do fórum correspondente ao código numérico e analisar cada um dos seus links (i.e., tópicos daquele fórum), usando o HPricot. Pra quem quiser saber mais, este tutorial de HPricot do Led Nerd mostra como criar um código que extrai dados de ações do Google Finance.
No nosso caso, temos que levar em conta outro detalhe: o phpBB divide os fórums em várias páginas, não havendo a possibilidade de listar todos os tópicos do fórum em uma página só. A solução é localizar a quantidade de páginas do fórum (o “Y” no texto “página X de Y”), usando o parâmetro de paginação do phpBB (start) para carregar cada uma delas. Algo como:
require 'open-uri' require 'hpricot' ... def scrape(forum_id, cidade_estado) url_forum = "http://url_do_site/phpbb2/viewforum.php?f=" + forum_id.to_s doc = Hpricot(open(url_forum)) numPaginas = doc.at("/html/body/table/tr/td/form/table[3]/tr[2]/td/span/b[2]").inner_text.to_i # Processa cada uma das páginas, buscando links com endereços for pagAtual in 1..numPaginas if pagAtual!= 1 doc = Hpricot(open(url_forum+"&start="+((pagAtual-1)*TOPICOS_POR_PAGINA).to_s)) end # Aqui temos a página atual, carregada e processada na variável doc end end
Note que é preciso determinar a quantidade de tópicos por página. Outros sites exigirão outras estratégias, – o importante é carregar o HTML relevante, uma página por vez. No nosso caso precisamos avaliar cada um dos links desta página, submetendo-o a uma expressão regular que determina a existência de um endereço físico no texto do link, e, quando há, extrai o mesmo e outras informações de interesse (ex.: telefone). No ponto onde temos o a página carregada, usamos:
doc.search("//a").each do |a| texto_link = a.inner_text match_endereco = /(Rua|R\.|Av\.|Av ).*?([,-] *[0-9]*|[0-9]+|\[|\{|\()/i.match(texto_link) endereco_limpo = match_endereco.to_s.gsub(/( *?[\,\-\(\{\[])*? *?$/, "") if match_endereco and (endereco_limpo.length>3) # Monta o link "limpo" para o endereço e verifica se tem no banco id_topic = /t=([0-9]*)/.match(a['href'])[1] url = "http://url_do_site/phpbb2/viewtopic.php?t=" + id_topic telefones = texto_link.scan(/[1-9][0-9]{3}-?[0-9]{4}/) # Aqui as variáveis endereco_limpo, cidade_estado, url e telefones # contém os dados de que precisamos end end
A reconstrução da url do tópico é necessária porque o phpBB anexa aos links um identificador de sessão sempre que o agente recuperador não aceita cookies de identificação (o que é o caso do Hpricot). A expressão acima seguramente pode ser melhorada (um dos seus maiores pontos fracos é não processar os endereços de Brasíla) – aceito sugestões! ;-)
Com isso mostramos como navegar e processar os dados de um site, encerrando o primeiro assunto. A seguir: geotagging.



Ei, eu postei sobre o bitchmaps no meu blog. Achei mt interessante, para além do tema.
A partir do seu projeto, realmente fiquei pensando em mil idéias possíveis com visualização de informações, locative media, etc.
[Responder]
Obrigado pelo post e pelo comentário! O seu blog está listado na seção “referências” (na página em português).
De fato, as aplicações são inúmeras. E o legal de fazer essa experiência foi ver que não é um bicho de sete cabeças. Claro, especialistas (tive a chance de conhecer um ou dois, e foi o que me animou para brincar com o assunto) sempre poderão ajudar em usos mais sofisticados, mas para agregar funcionalidades geográficas básicas não tem muito mistério.
A parte que envolveu mais código, pasme, é essa que eu publiquei agora, o resto é bem minimalista), aguarde! :-)
[Responder]