Criando Web crawlers em Python – Parte II
No post anterior, eu mostrei como recuperar informações básicas de uma página da Web usando urllib, urllib2 e BeautifulSoup. Neste post eu mostrarei como enviar dados via GET e POST.
Há um excelente guia sobre urllib2: o urllib2 - The Missing Manual que foi de grande valia nos meus estudos. Nele, pode-se encontrar as informações sobre envio de informações do tipo GET e POST para o servidor. Isso será útil, pois é assim que a busca do SourceForge (e da maioria dos outros portais) funcionam.
Vamos dar uma olhada na URL do na pesquisa por "Python" no SF:
http://sourceforge.net/search/?type_of_search=soft&words=python
Bem, isso indica que temos que enviar duas variáveis para o SF.net: type_of_search (sempre igual à soft) e words (que é a nossa busca). Um exemplo de código que faria essa busca poderia ser esse:
import urllib,urllib2
base_url = 'http://sourceforge.net'
busca = raw_input('Pesquisar por: ')
variaveis_get = urllib.urlencode({'type_of_search':'soft','words':busca})
req = urllib2.Request(base_url+'/search/',variaveis_get)
response = urllib2.urlopen(req).read()
print response
Se der tudo certo, você deveráver o código fonte da página do SF que contém os resultados da pesquisa.
Feito isso, vamos analisar como a marcação do SF é organizada na busca:
O Firebug nos ajuda a não perder tanto tempo assim analisando código. De acordo com o que pudemos obter, podemos dividir as informações que queremos dessa forma:
- O resultado da busca está dentro de uma tabela com o id="searchtable".
- Um resultado da busca está sempre dentro de um td com classe description.
- O nome do projeto está dentro de um link dentro de um h2 dentro desse td "description".
- O link de download se encontra na próxima td da mesma linha da tabela.
Exibindo as informações dos projetos
Agora nós temos como buscar nossa informação e como filtra-la. Como disse no post anterior, eu iria mostrar alguns usos mais avançados do BeautifulSoup, principalmente no que diz respeito à percorrer o documento. O próximo passo será mostrar as informações dos projetos que aparecem no resultado da nossa busca. Podemos fazer isso da seguinte forma:
from BeautifulSoup import BeautifulSoup
import urllib,urllib2
base_url = 'http://sourceforge.net'
busca = raw_input('Pesquisar por: ')
variaveis_get = urllib.urlencode({'type_of_search':'soft','words':busca})
req = urllib2.Request(base_url+'/search/',variaveis_get)
response = urllib2.urlopen(req).read()
soup = BeautifulSoup(response)
tabela = soup.find('table',{'id':'searchtable'}) # procura pela tabela com id=searchtable
linhas_tabela = tabela.findAll('tr') #retorna uma lista com todas as linhas (<tr>) da tabela
i=0
for linha in linhas_tabela:
i+=1
coluna_descricao = linha.find('td') #encontra a primeira coluna (descricao) da linha
nome_projeto = coluna_descricao.find('a').contents[0] #o atributo contents contem uma lista com o conteudo da tag
descricao_projeto = coluna_descricao.contents[2].strip()
print 'Projeto '+str(i)+': '+nome_projeto
print descricao_projeto
print '--------------------------------'
Legal, não? Isso é uma demonstração básica do poder que o BeautifulSoup tem para percorrer e extrair informações em (X)HTML. O mais interessante de tudo é que quase todos os métodos da classe BeutifulSoup.Tag retornam a própria referência, o que quer dizer que podemos fazer chains (cadeias) de comando como essas:
nome_do_primeiro_projeto_da_pesquisa = soup.find('table',{'id':'searchtable'}).findAll('tr')[0].find('td').find('a').contents[0]
Isso é realmente muito útil quando se quer economizar código. E o mais interessante de tudo: é bem legível (mas não necessariamente fácil de interpretar... quem ler seu código assim terá que ter uma boa noção do BeautifulSoup).
Baixando os arquivos do projeto
Essa parte é relativamente fácil. O SF é um serviço muito bom, até para o nosso pequeno experimento
A URL que o link "Download Now" aponta sofre 3 redirects até o arquivo final. Tudo isso serve para o SF determinar a sua localização e apontar o mirror mais próximo de você. Então a única coisa que precisamos fazer é passar esse link para o wget (um programa bem útil para download de arquivos na Web presente na maioria das distribuições Linux. Um clone para Windows pode ser encontrado aqui) e ele irá baixar o projeto para nós. Simples, não?
Então vamos ao código:
from BeautifulSoup import BeautifulSoup
import urllib,urllib2
from subprocess import call
base_url = 'http://sourceforge.net'
busca = raw_input('Pesquisar por: ')
variaveis_get = urllib.urlencode({'type_of_search':'soft','words':busca})
req = urllib2.Request(base_url+'/search/',variaveis_get)
response = urllib2.urlopen(req).read()
soup = BeautifulSoup(response)
tabela = soup.find('table',{'id':'searchtable'}) # procura pela tabela com id=searchtable
linhas_tabela = tabela.findAll('tr') #retorna uma lista com todas as linhas (
) da tabela
i=0
links_download = []
for linha in linhas_tabela:
i+=1
coluna_descricao = linha.find('td')
nome_projeto = coluna_descricao.find('a').contents[0]
link_download = linha.find('a',{'class':'downloadnow'})['href']
links_download.append(link_download)
descricao_projeto = coluna_descricao.contents[2].strip()
print 'Projeto '+str(i)+': '+nome_projeto
print descricao_projeto
print '--------------------------------'
opcao = int(raw_input('Qual projeto gostaria de baixar? '))
opcao_url = base_url+links_download[opcao-1]
call(['wget',opcao_url]) #chama o wget com o link para download
A única coisa que eu fiz de novo foi adicionar o link de download em uma lista e perguntar ao usuário qual projeto ele deseja baixar. Então, eu passei o link de download para o wget que fez o serviço de baixar o arquivo pra mim. Pronto! Nosso crawler de exemplo do SF.net está pronto
No proximo post eu mostrarei como guardar valores de sessão para percorrermos páginas protegidas por senha. See ya!
Criando screencasts no Linux
Eu vi algumas pessoas reclamando sobre a dificuldade de criar screencasts usando Linux. É, eu também tive algumas dificuldades. Nenhuma das ferramentas que eu consegui encontrar com minhas buscas não resolviam meu problema:
- Istanbul - Bonitinho, parece leve, grava legal, mas simplesmente dá um erro de IO (não entendi direito qual era o erro ) e não salva.
- xvidcap - Levíssimo, completo, mas tem problema com o PulseAudio e não grava o áudio do microfone. Tentei inicializa-lo com o comando padsp xvidcap, mas também não resolveu: mesmo chiado no som.
- recordMyDesktop - Grava áudio normal, mas as imagens tinham uma qualidade horrível. Parecia que ele atualizava uma parte da tela e esquecia da outra. Resultado: eu gravava um teste no VIM na linha de comando (preta) e quando eu passava pro GMail (verde) a tela ainda continuava preta...
Não sei o por quê, mas nem todos os programas que vem no Linux vêm com as configurações mais comuns já de cara. O problema é que eu não percebi que o recordMyDesktop encaixaria perfeitamente pra mim se ele já viesse com uma opção já marcada desde o início:
Estranho, não? Eu pensava que marcar "Full shots at every frame" resolveria meu problema, mas, inexplicavelmente, o "Encode On the Fly" resolveu. Apesar da tooltip dizer que essa opção consome mais poder de processamento, eu não notei tanto. A qualidade do vídeo ficou impecável e eu estou com os efeitos no máximo
Se após gravado o screencast você desejar edita-lo ou converte-lo, há uma gama de softwares para Linux mesmo que fazem isso. Para conversão eu uso muito o ffmpeg (não conheço nenhum outro melhor e mais completo) e me recomendaram o Avidemux para edição de vídeo. A única coisa que eu sinto falta agora é algo que coloque os caracteres digitados na tela e que destaque o clique do mouse.
See ya!
Testes unitarios no Flex usando o FlexUnit 4
O FlexUnit 4 é a mais nova versão (não tem a oficial, só a RC, por enquanto) e apresenta uma série de vantagens sobre o seu antecessor, o FlexUnit 0.9, como os metadados de teste ([Test], [After] e [Before], para citar as mais populares), Theories, DataPoints e Assumptions que são úteis para testar grandes quantidades (talvez até infinita) de dados e ver como a aplicação se comporta e a possibilidade de executar os testes com diferentes Runners.
Este tutorial tem como objetivo mostrar o básico de testes unitários no Flex, sem se aprofundar muito nos recursos avançados do framework de testes. Eu pretendo ir postando mais tutoriais à medida que eu for me aprofundando na tecnologia.
O setup
Para usar o FlexUnit4, você precisa baixa-lo aqui. Após isso, crie um projeto no Flex Builder e adicione todas as libs que vieram no pacote no seu diretório libs:

Como o bom e velho TDD manda, vamos primeiro escrever a classe de teste de exemplo antes de escrever nosso código de produção.
A classe de teste
Uma classe de testes é uma classe comum que usa a classe Assert para fazer asserções. No exemplo que irei mostrar, usarei dois tipos básicos de asserção, mas se você observar, o FlexUnit possui vários tipos diferentes de asserções:
package tests
{
import org.flexunit.Assert;
import org.flexunit.runner.manipulation.filters.IncludeAllFilter;
import production.BasicClass;
public class BasicTests
{
public function BasicTests(){}
private var basicClass:BasicClass;
[Before]
public function before():void
{
basicClass = new BasicClass();
}
[Test]
public function Verifica_Se_As_Duas_Strings_Sao_Iguais():void
{
var str:String = "MinhaString";
Assert.assertTrue(basicClass.areStringsEqual(str,"MinhaString"));
}
[Test]
public function Verifica_Se_A_Soma_Retorna_Resultado_Correto():void
{
var soma:int = 10;
Assert.assertEquals(soma,basicClass.somar(2,8));
}
[After]
public function after():void
{
//codigo de after
}
}
}
A suíte de teste
A suíte de testes inclui nosso caso de teste descrito acima e será útil para o Flex executar nossos testes. Sendo assim, nossa suíte de testes ficaria mais ou menos desse jeito:
package tests
{
[Suite]
[RunWith("org.flexunit.runners.Suite")]
public class MyTestSuite
{
public var baseTest:BasicTests;
public function MyTestSuite(){}
}
}
UITestRunner e o FlexUnitCore
O UITestRunner é um componente do FlexUnit que mostra os testes numa interface gráfica. Ele ficará na nossa aplicação e mostrará os resultados dos testes.
O FlexUnitCore será o responsável por carregar as suítes de teste e por passar os dados de saída de testes para o UITestRunner. No nosso caso, nossa aplicação principal ficaria assim:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="run()" layout="absolute" xmlns:flexUnitUIRunner="http://www.adobe.com/2009/flexUnitUIRunner">
<mx:Script>
<![CDATA[
import tests.MyTestSuite;
import org.flexunit.listeners.UIListener;
import org.flexunit.runner.FlexUnitCore;
public var core:FlexUnitCore;
public function run():void
{
core = new FlexUnitCore();
core.addListener(new UIListener(uiRunner));
core.run(MyTestSuite);
}
]]>
</mx:Script>
<flexUnitUIRunner:TestRunnerBase id="uiRunner" width="100%" height="100%"/>
</mx:Application>
O código de produção
Ufa! Depois de escrever a classe de teste, a suíte de teste e o runner, podemos nos focar em fazer nosso código de produção
. Dêem uma olhada em como ficaria o dito:
package production
{
public class BasicClass
{
public function BasicClass(){}
public function areStringsEqual(string1:String,string2:String):Boolean
{
return (string1==string2);
}
public function somar(valor1:int,valor2:int):int
{
return valor1+valor2;
}
}
}
E Voilà!
Depois de tudo pronto, a cara da criança ficaria mais ou menos assim:
Legal, não? E ainda dá pra fazer com que o FlexUnit4 exporte o resultado dos testes para um arquivo XML, permitindo que seus testes no Flex sejam importados pelo seu sistema de Integração Contínua, mas isso é assunto para outro post
Você pode baixar o código fonte aqui e ver os exemplos rodando online aqui.
Good testing!


