Mês: maio 2008

Webcomics em dobro!

20 de maio de 2008

Excelente início de semana para webcomics: não apenas Rene Engström retomou a produção de Anders Loves Maria (após um período assustada por conta de um idiota passador de trotes) mas também achei outro quadrinho muito simpático: Wasted Talent, da (supostamente) canadense Angela Melick.

Wasted Talent

Aproveitando para confirmar a quem me perguntou: sim, Rene é uma moça, apesar do nome sugerir um mancebo. Você pode vê-la no vídeo que mostra a vizinhança na qual se passa Anders Loves Maria e conhecer mais sobre ela no seu LiveJournal (uma ferramenta que se justifica quando alguém genuinamente interessante usa – gente que faz algo assim).

Nintendo Uí

13 de maio de 2008

miwi_wii_knock_off-1.jpgA criatividade chinesa não parou no PolyStation – estão chegando no Brasil os clones baratinhos do Nintendo Wii. Veja os detalhes e prepare-se para a AÇÃO!

Desenvolvimento para iPhone: instalando o toolchain

11 de maio de 2008

iphoneA capacidade de hardware e software já fazem do iPhone uma plataforma atrativa para criar aplicativos. É difícil falar em número de usuários no Brasil – oficialmente são zero, tem relatório dizendo que são um mol. Mas o fato é que os aparelhos são populares entre os formadores de opinião (basta observar a enxurrada deles em qualquer evento sobre web), o que garante um público no mínimo interessante.

Este post dá uma visão superficial das alternativas para desenvolvimento mais populares e documenta as dificuldades que encontrei para colocar no meu Mac o conjunto de ferramentas livres que permite compilar aplicações.

Existem (pelo menos) três caminhos para construir aplicações para o iPhone:

  • iPhone Web Apps

    A idéia é usar tecnologias web (HTML, JavaScript, CSS, AJAX, server-side scripts, etc.), hospedando as aplicações remotamente e rodando via Mobile Safari. Existem guidelines oficiais, livros (gostei deste) e pelo menos dois frameworks (iUI e jPint) que ajudam a tornar as aplicações web parecidas com as nativas – os resultados são surpreendentemente bons.

    As desvantagens são as mesmas de qualquer aplicação web: acesso limitado ao sistema (dá pra saber se o fone está em pé ou deitado, fazer chamadas e acessar o Maps, mas não tem como ler o acelerômetro ou acionar o iPod, entre outras coisas) e a necessidade de estar online para usar a app (agravada no Brasil, onde a cobertura de WiFi é mais restrita e os planos de dados tornam caro usar o EDGE).

    A facilidade de desenvolvimento, distribuição e instalação falam alto, tornando este caminho uma excelente opção para aplicações de uso casual, ou que dependam fortemente de recursos online (agregadores de dados, clientes/front-ends para sites e assemelhados).

  • Apple iPhone SDK

    O SDK da Apple é tentador: o beta é baseado numa IDE amigável (XCode), e gera aplicações que irão rodar em qualquer iPhone – quando for lançado definitivamente. O mais provável é que elas sejam distribuídas via iTunes, viabilizando a venda de software a baixo custo.

    Nem tudo são flores: é preciso se familiarizar com Objective C, e tudo o que for desenvolvido tem que ser aprovado pela Apple – que impõe restrições técnicas (nada de processos em segundo plano) e de domínio (p0rn está vetado). Além disso, o XCode só roda em Mac, e (o problema mais sério): ainda não é possível distribuir as aplicações.

  • iphone-dev toolchain + installer.app

    O Installer é uma aplicação conhecida de que tem iPhone desbloqueado: ele permite localizar e instalar centenas de pacotes de software gratuito (boa parte sendo software livre). Estes programas são criados, em sua maioria, através do iphone-dev toolchain – um kit baseado em ferramentas livres para desenvolvimento UNIX (essencialmente gcc/make, usando llvm para viabilizar a compilação cruzada) e empacotados em um formato que o Installer.app entende.

    A única restrição é que o usuário tem que ter um iPhone desbloqueado. Você também fica sujeito ao esquema de distribuição do Installer – um pequeno risco a correr, mas é razoável crer que continuará funcionando no futuro, e que será menos restrito que a proposta da Apple.

Para começar a brincar eu optei pela terceira via. É um caminho meio tortuoso, já que é preciso baixar/compilar todo o software do qual o iphone-dev depende. Na real eu descobri (meio tardiamente) que é possível baixar o toolchain pré-compilado para Mac OS X Leopard – mas compilar ainda é o único caminho para quem usa Windows/Linux/Mac OS X Tiger.

Teoricamente basta seguir o passo-a-passo oficial – na prática, a teoria é outra. Por exemplo, eu já tinha o llvm instalado via DarwinPorts, mas a ferramenta exige uma versão em particular, então tive que desinstalar primeiro. Da mesma forma, para compilar o odcctools foi preciso prestar atenção ao issue 31. De fato, a lista de issues ajuda um bocado nessas horas.

O link que informa como obter uma cópia do sistema de arquivos do iPhone está quebrado. Um método razoável é baixar da Apple, só que é preciso des-criptografar, seguindo estas instruções. Outra alternativa é copiar instalando o OpenSSH e usando o scp a partir do micro, na forma scp -Cr root@ip.do.seu.iphone:/Diretório ., para cada diretório desejado). Tive que fazer isso para um ou dois arquivos que deram problema com o outro esquema.

Por falar em OpenSSH, eu achei meio sinistro ter um servidor ssh rodando sendo que as senhas dos dois usuários (mobile e root) são as mesmas em todos os iPhones, e resolvi mudar. NÃO FAÇA ISSO! – o aparelho entoru em loop (provavelmente o SpringBoard ou algum programa dependente tem uma chave de login chumbada, e usar o passwd atropela ela), e pra voltar só restaurando o /etc/passwd original – o que só foi possível porque o OpenSSH estava lá.

Um ponto que me assusta um pouco no passo-a-passo é um certo excesso de sudo. E não sei exatamente o que eu fiz de errado, mas algum desses passos sobrescreveu um arquivo de sistema do meu Mac (era um binário universal que passou a só ter o código ARM), deixando o micro inutilizado até que eu dei boot pelo DVD de instalação (nem o boot single-mode funcionava mais) e copiei o arquivo de volta. Esteja preparado para este tipo de problema.

A última parte (que é o build final dos binários) enroscou por conta de algum problema acontecido na criação dos headers. Até consegui concluir o processo copiando alguns .h do SDK beta do iPhone, mas ao tentar dar build das aplicações novos problemas eram encontrados. Solução: baixei o toolchain pré-compilado e copiei o diretório de headers dele sobre o meu.

Com isso foi possível compilar um hello world básico e rodar no telefone. Ainda é preciso empacotar para o Installer, mas isso é uma segunda fase.

Agora é só programar! :-)

UPDATE: O kit também funciona direitinho no Linux (Ubuntu 8.04 num Eee PC). Eu segui este outro guia passo-a-passo e a coisa rolou praticamente sem problemas (os poucos que tive estão documentados nos comentários do guia).

Daltony: Sou eu quem come

7 de maio de 2008

A Dani tirou uma foto tão engraçada da minha pessoa com a boca cheia de biscoitos Daltony que não foi possível evitar o comentário (mesmo considerando que o mestre é assunto inesgotável na web). Para quem não conhece, Daltony é um empresário cuja maneira peculiar de apresentar a si mesmo e a seus produtos tornou-o um dos memes clássicos da internet brasileira.

Sempre tive curiosidade de conhecer os famosos biscoitos, e a oportunidade surgiu por acaso em um supermercado na Vila Madalena, que tinha uma seção forrada com caixas e caixas do mesmo. A diversão começou nas caixas, com o (novo?) slogan “é top de linha”.

Levei pra casa (e, no dia seguinte, para o escritório) os sabores natural e doce. Fiquei tentado a levar o “Daltony Bem Magro”, mas o biscoito já era bem mais caro que os concorrentes, e achei mais prudente lmitar a primeira experiência.

Em resumo: o natural era meio esquisitinho, mas do doce eu gostei. Volto em breve para comprar mais. Acho que, em termos de alimentos exóticos, agora só me falta o churrasco grego (com molho de gaveta e suco grátis).

Sua privacidade vale um chocolate?

6 de maio de 2008

Fui supreendido pelo panfleto da ilustração ao entrar no prédio de uma grande empresa de mídia paulistana. A proposta é que a pessoa entregue dados pessoais, vínculo com a empresa, informações do veículo e da residência para uma empresa de seguros – tudo por um singelo chocolate.

Absurdo? Não para quem lembra daquela pesquisa segundo a qual 70% das pessoas entrevistadas trocariam até suas senhas pessoais por uma barra de chocolate. O absurdo é o pessoal da “segurança” de TI ser tão cheio de regras e segredos, mas autorizar a distribuição de um formulário que já vinha até com o domínio do e-mail corporativo preenchido. Na próxima promoção é só oferecer uma jujuba e pedir a senha.

Anders Loves Maria

6 de maio de 2008

Este deveria ser um artigo a respeito de Anders Loves Maria, um webcomic que trabalha o universo dos “jovens adultos” através das relações pouco usuais entre os personagens-título e o elenco de apoio, tudo isso apoiado em uma arte que consegue ser experimental sem perder a consistência e a qualidade.

Infelizmente, o motivador é outro: após mais de 150 episódios, a autora Rene Engström (que diz em entrevista produzi-los originalmente no idioma sueco, traduzindo para o inglês logo em seguida) decidiu suspender a publicação porque um indivíduo não-identificado começou a persegui-la pelo telefone.

É difícil saber se a perseguição tem a ver com os quadrinhos (ela deu mais detalhes por esses dias, alegando que a perseguição telefônica não era o último motivo), mas a julgar pelo conteúdo da tira (que aborda temas um pouco polêmicos, mas sem sensacionalismo ou exageros gráficos), e considerando o estado de trevas em que se encontra o mundo, não duvido que haja correlação entre as coisas.

Claro que fico chateado por não poder mais acompanhar a história com a qual criei uma relação, mas o que me deixa realmente contrariado é pensar que gente assim faz o que quer e sai impune. De qualquer forma, recomendo a leitura desde o início. Quem sabe até você chegar na última tira a autora conseguiu superar essa dificuldade? Eu estou torcendo.

A receita de bolo do BitchMaps (parte 3 de 3)

2 de maio de 2008

Considerando que já temos uma coleção de endereços capturados e geocodificados, o natural é mostrá-los:

Parte III – Exibindo Mapas

Assim como existem vários serviços gratuitos de geocoding, outros tantos permitem exibir pontos arbitrários, embutindo o mapa no seu site e deixando o usuário navegar livremente dentro dele. Novamente temos que obter uma chave e usar uma API especializada – e continua valendo a sugestão de usar uma biblioteca que abstraia as diferentes APIs.

A que eu mais gostei foi o Mapstraction – que não é exclusivo de Ruby, podendo ser usada em qualquer plataforma (a “mágica” acontece no front-end, via Javascript). O site tem exemplos com vários provedores, e compensa dar uma olhada.

O caminho adotado no Bitchmaps foi outro: ao invés de embutir o mapa, o site exporta os endereços como KML, um formato de dados que permite representar localizações geográficas e seus metadados (descrições, telefones, endereços físicos, URLs, etc.). Vários softwares e sites são capazes de ler KML e exibir seus pontos num mapa (incluindo aí iPhone, que foi o principal atrativo).

O KML nada mais é que um XML com tags específicas. Veja um exemplo (extraído da Wikipedia):

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
  <Placemark>
    <description>New York City</description>
    <name>New York City</name>
    <Point>
      <coordinates>-74.006393,40.714172,0</coordinates>
    </Point>
  </Placemark>
</kml>

No Rails você pode gerar esse tipo de XML usando um template .rxml (ou, de forma mais contemporânea, um template .builder):

xml.instruct! :xml, :version=>"1.0"
xml.kml(:xmlns=>"http://earth.google.com/kml/2.1"){
  xml.Document {
    xml.name(@name)
    for place in @places
      xml.Placemark {
        xml.name(place.description)
        xml.description(place.url)
        xml.Point {
          xml.coordinates(place.lng.to_s+','+place.lat.to_s)
        }
        for phone in place.phones
          xml.phoneNumber(phone.phone)
        end
      }
    end
  }
}

Agora é só chamar este template colocando em @name o título do documento e em @places os locais capturados no banco. Neste exemplo, eles devem possuir as propriedades description, lat, lng e uma coleção phones contendo a propriedade phone (os nomes estão em inglês por mera conveniência de usar o ActiveRecord out-of-the-box sem mapear tabelas ou lançar mão de magia negra).

No caso do BitchMaps, eu chamo o arquivo acima de layer.rxml, e uso um código bem trivial no controller:

def layer
  filtro = ""
  case params[:id]
    when "sp"
      filtro = "Sao Paulo - SP"
    when "camp"
      filtro = "Campinas - SP"
    ..
  end
  @name = "BitchMaps"
  if filtro!=""
    @name = @name + " - " + filtro
  end
  @places = Place.find(:all, :conditions => ["city_state = ? and full_address is not null", filtro])
  #render :xml => @places.to_xml
  respond_to { |format|
    format.kml {}
  }
end

Com isso, ao chamar, por exemplo, http://localhost:3000/bm/layer?id=sp (bm é o ID do controller), eu obtenho o KML de São Paulo – SP.

Faltam apenas dois detalhes: o meu provedor é pobrinho e não suporta Ruby/Rails, e eu queria usar KMZ (a versão “zipada” do KML). Resolvo os dois problemas de uma só vez com um shell script que salva em arquivo e comprime cada uma das cidades:

#/bin/bash
# Salva os XMLs de cada cidade em arquivos kml
lista="sp camp cur lon poa rj bh"
rm published/kmz/*
for cidade in $lista; do wget http://localhost:3000/bm/layer/$cidade.kml -O published/kmz/$cidade.kml; done
# Transforma os kml em kmz
cd published/kmz
for cidade in $lista; do zip -m $cidade.kmz $cidade.kml; done

Daí é só copiar os arquivos .kmz para o servidor, no qual um arquivo .htaccess customizado ajuda o Apache a servir os arquivos com o tipo correto (para orientar o browser) e a redirecionar os nomes no formato bitchmaps.com/XX para bitchmaps.com/kmz/XX.kmz:

AddType application/vnd.google-earth.kml+xml .kml
AddType application/vnd.google-earth.kmz .kmz
RewriteEngine on
RewriteRule (sp|camp|cur|lon|poa|rj|bh)\/?$ http://bitchmaps.com/kmz/$1.kmz [R]

Enfim, é isso. Espero que esta série tenha matado a curiosidade de quem viu o BitchMaps e também que ajude a colocar mais informação geográfica na web. Até a próxima!

A receita de bolo do BitchMaps (parte 2 de 3)

2 de maio de 2008

Na primeira parte desta série vimos alguns caminhos para esquadrinhar um site em busca de endereços físicos. A identificação destes endereços no meio de textos é um assunto complexo, que ficamos longe de esgotar. Mas é hora de seguir em frente, o que nos leva à…

Parte II – Geocoding

Seja através de dados alheios ou do seu próprio cadastro, você já deve ter um banco de dados de endereços físicos, e provavelmente gostaria de mostrá-los em um mapa. Antes disso isso é preciso executar um passo: transformar cada um destes endereços em coordenadas globais (latitude e longitude). Este processo é conhecido como geocoding.

Existem vários geocoders, isto é, sites que fazem esta transformação. Eles costumam permitir a transformação de uma quantidade limitada de endereços por período de tempo gratuitamente, passando a cobrar quando o volume é maior. Por conta disso, alguns vão exigir que você cadastre sua aplicação para obter uma “chave” que identificará as chamadas vindas dela. Este passo varia de site para site.

Para ter flexibilidade na escolha de um geocoder, é recomendável codificar sua aplicação de forma independente do serviço escolhido. No caso do BitchMaps foi crucial trabalhar assim, já que, embora não passe de uma experiência bem-humorada, alguns geocoders poderiam se recusar a processar os endereços devido à natureza dos mesmos.

É aí que entra o GeoKit, um plug-in para Rails que faz essa ponte entre sua aplicação e o geocoder. Ele tem várias capacidades, tais como calcular distâncias e localizar pontos dentro de um raio de distância, mas o que vamos usar mesmo é o básico: transformar um endereço textual em coordenadas globais.

Para fazer isso basta chamar a classe corespondente ao geocoder que você quer usar. Todas essas classes têm como base a classe Geocoder, portanto, se você mudar de classe no futuro, não é preciso alterar o restante do código.

O arquivo geocoders.rb contém as classes possíveis, a saber:

# * Google Geocoder - requires an API key.
# * Yahoo Geocoder - requires an API key.
# * Geocoder.us - may require authentication if performing more than the free request limit.
# * Geocoder.ca - for Canada; may require authentication as well.
# * IP Geocoder - geocodes an IP address using hostip.info's web service.
# * Multi Geocoder - provides failover for the physical location geocoders.

Digamos que você vá usar o Yahoo Geocoder (é só um exemplo – note que nem todos os geocoders funcionam em todos os países/cidades – teste com endereços gringos primeiro). Você entra no site para conseguir uma chave para a sua aplicação, abre o environment.rb e substitui a chave na linha:

GeoKit::Geocoders::yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'

Uma vez feito isso, basta ter o endereço em mãos e chamar a biblioteca. Se o endereço for encontrado, você terá latitude e longitude (além de outras informações). No caso do BitchMaps, a chamada só é feita se o tópico ainda não estiver no banco de dados (para evitar a busca de endereços já geocodificados ou que não foram reconhecidos). E o código para buscar é tão simples quanto:

# endereco_limpo e cidade_estado foram obtidos do fórum
searched_address = endereco_limpo + " - " + cidade_estado + ", BRA"
loc = YahooGeocoder.geocode(place.searched_address)
if loc.success
  lat = loc.lat
  lng = loc.lng
  end_norm = loc.full_address
  # lat e lng contém a latitude/longitude, e end_norm o endereço "normalizado"
end

Experimente um pouco com este código (sugestão: use o console da sua aplicação), tentando diferentes endreços e geocoders. Você pode testar os pontos encontrados digitando os valores de latitude e longitude neste site.

Recomendo guardar o endereço normalizado (isto é, no formato padrão do site) no banco de dados, para ajudar em uma eventual limpeza da base no futuro. Não fiz isso no BitchMaps, mas você provavelmente vai querer: há casos em que parte do nome da rua ou o nome do bairro são confundidos com a cidades, entre outros problemas. Se você tiver o CEP, verifique o formato em que o GeoCoder o devolve no endereço normalizado e use-o – ajuda um bocado!

Foi mais fácil que capturar os endereços, não? A única ressalva é que, assim como você tomou cuidado para respeitar os limites do site, é bom prestar atenção nos limites e termos de serviço do geocoder utilizado, especialmente se a sua aplicação crescer. E em breve vem a última parte, na qual finalmente veremos um mapa com endereços. Até mais!

A receita de bolo do BitchMaps (parte 1 de 3)

1 de maio de 2008

É 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.