Archive for March, 2010
Is Google Chrome really faster than IE and FF? Well, not always…
For some reason, Google Chrome (or just Chrome) is slower than Firefox and even Internet Explorer in at least one situation: simple numeric calculus.
In past 2007, I was in calculus class in my University and my teacher proposed an extra job (like a challenge) involving calculus and programming to my class. So I proposed a simple numeric curve-area calculator using Trapezoidal Rule using metaprogramming principles.
Well, I did an PHP and JavaScript version of my algorithm. JavaScript was too slow that time (FF 3.0 and IE7), so I presented the PHP (faster) version of my algorithm to my teacher.
Time passed, Google released the first version of their browser and I noticed all excitement about its velocity in processing JavaScript code, thanks to V8 engine. I said “Whoa, this is great! Perhaps my old JavaScript code may be faster than PHP one i did in 2007!”. Well, this post is to tell I was very, very wrong about that.
Before anything, I would like to show a functional example of my algorithm.
You can try by typing some function in function’s textbox to integrate and clicking “Integrate” button or you can cick “Execute tests” to integrate all the pre-programmed functions.
The algorithm calculates an approximation of the area under a curve expressed by a function using the trapezoidal rule, which is very simple: it basically consists in splitting the area in trapezoids and summing all of them. More splits, more accuracy.
Executing all test functions in three different browsers, I got these results (lower is better):
All the problems is described in the test script. All the tests were executed in a Dell Inspiron 1545 notebook with 4GB of RAM and a Core2Duo P8600 @2.4 GHz processor running Windows 7 Home Basic and Google Chrome was the slowest in most tests. I did the very same tests (except for IE and Safari and including Epiphany) using Ubuntu 10.04 and Google Chrome was the slowest one too. I’d like to here you if you have more results with different browsers.
I Googled was happened here and I found no relevant results. It seems the problem is with V8 engine, because Epiphany and Safari (which uses SquirellFish as JavaScript engine) were too much faster than Chrome. If you have an idea on what Google Chrome is slower than others in these tests, please, let us know ![]()
P.S: this is my first post in English, so I’m sorry for any mistake ![]()
P.P.S: I know today is April 1st, but this is not a joke.
Criando Web crawlers em Python – Parte III
Ir para Criando Web crawlers em Python – Parte I
Ir para Criando Web crawlers em Python – Parte II
Na parte II desta série de tutoriais, eu mostrei como criar um mini crawler para o SourceForge.net, com suporte à pesquisa e download de softwares. Neste post, eu irei mostrar como gerenciar sessões com o Python e um downloader de legendas do legendas.tv como exemplo de uso.
Bem, antes de mais nada, o legendas.tv não tem um robots.txt (pelo menos não ao momento que escrevo este post) e como tudo que não é expressamente proibido é permitido, eu posso sem nenhum problema, criar um crawler para baixar legendas de lá. No entanto, eu mandei um email falando sobre a minha experiencia e pedindo permissão. Felizmente, o pessoal do legendas.tv permitiu que eu fizesse o crawler usando-os como cobaias ![]()
1 – Gerenciando sessões no Python.
HTTP é um protocolo stateless. Isso quer dizer que ele não guarda o estado da aplicação da mesma forma que um programa que você tem instalado no seu computador faz, por exemplo. Podemos enviar nosso nome de usuário e senhas pro legendas.tv (ou para qualquer outro site). No entanto, há formas de guardar o estado da sua aplicação Web e uma dessas formas é através de cookies. O cookielib do Python faz esse trabalho de gerenciar cookies e faz de uma forma relativamente fácil. Eis um exemplo adaptado da página de documentação do cookielib:
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)
request = urllib2.Request("http://www.example.com")
Apesar do código acima ser um pouco obscuro pra entender, ele basicamente cria um handler (o opener) para lidar com os cookies. A partir daí é só usar o opener para fazer requisições. Podemos enviar nosso nome de usuário e senhas para o portal e automaticamente o opener enviará os cookies comprovando que você está logado. Simples, não? Agora é só enviar nosso usuário e senha para o legendas.tv e sair crawleando:
import cookielib, urllib2,urllib
from BeautifulSoup import BeautifulSoup
import sys
import re
base_url = 'http://legendas.tv'
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)
username = raw_input('Login: ')
password = raw_input('Senha:')
login_data = urllib.urlencode({'txtLogin':username,'txtSenha':password})
request = urllib2.Request(base_url+'/login_verificar.php',login_data)
response = urllib2.urlopen(request).read()
if response.__contains__('Dados incorretos'):
print 'Ooops dados incorretos...'
raw_input('Pressione uma tecla para sair...')
sys.exit()
else:
print 'Logado com sucesso!'
Whoa! Conseguimos logar e estamos prestes a fazer a busca!
2 – Fazendo a busca
A busca consiste em três campos:
Assim como fizemos no login, faremos agora na busca: colocar os dados da busca num dicionário e passar como parâmetro para o urllib2.Request:
import cookielib, urllib2,urllib
from BeautifulSoup import BeautifulSoup
import sys
import re
base_url = 'http://legendas.tv'
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)
username = raw_input('Login: ')
password = raw_input('Senha:')
login_data = urllib.urlencode({'txtLogin':username,'txtSenha':password})
request = urllib2.Request(base_url+'/login_verificar.php',login_data)
response = urllib2.urlopen(request).read()
if response.__contains__('Dados incorretos'):
print 'Ooops dados incorretos...'
raw_input('Pressione uma tecla para sair...')
sys.exit()
else:
print 'Logado com sucesso!'
print 'Fazendo busca:'
print '--------------'
busca = raw_input('Buscar por: ')
tipo = raw_input('Tipo:(1 - Release,2 - Filme, 3- Usuário):')
idioma = raw_input('Idioma: (1 - Português,2 - Ingles,99 - Todos)')
search_dict = {'txtLegenda':busca,'selTipo':tipo,'int_idioma':idioma}
search_data = urllib.urlencode(search_dict)
request = urllib2.Request(base_url+'/index.php?opcao=buscarlegenda',search_data)
response = urllib2.urlopen(request)
page = response.read()
soup = BeautifulSoup(page)
Eu não coloquei todas as opções de idioma para manter o código simples. Só lembrando que esses códigos foram retirados do atributo value dos options do select int_idioma.
A partir de agora é só brincar um pouco com o BeautifulSoup que conseguiremos extrair todos os dados que quisermos. Eis o exemplo:
i = 0
list_download_ids = []
for span in span_results:
# há dois tipos de span na busca. Este não contem o que a gente quer
if span.attrs == [('class', 'brls')]:
continue
td = span.find('td',{'class':'mais'})
#parent encontra o elemento pai.
#ex: td.parent aponta para tr e tr.parent aponta para table
release = td.parent.parent.find('span',{'class':'brls'}).contents[0]
sub_name = td.contents[0].contents[0]
downloads = td.contents[5]
comentarios = td.contents[7]
avaliacao = td.contents[10]
data = span.findAll('td')[2].contents[0]
#recupera o ID do download que tá no código javascript
download_id_js = td.parent.parent.attrs[1][1]
download_id = re.search('[a-z0-9]{32}',download_id_js).group(0)
list_download_ids.append(download_id)
i+=1
print 'Opção: '+str(i)
print 'Serie: '+sub_name
print 'Release: '+release
print 'Downloads: '+downloads
print 'Comentarios: '+comentarios
print 'Avaliacao: '+avaliacao
print 'Data: '+data
print '-------------------------------------'
A estrutura da página do legendas.tv é um pouco complicada, por isso nós temos que fazer alguns workarounds (nome bonito para gambiarra) para conseguirmos extrair todos os dados. Um exemplo disso é como eu fiz para pegar o id dentro do código JS inline na página. Isso deixa nosso mini-crawler um pouco mais complexo de entender (de fato é um dos mais complexos que já mexi ate hoje).
3 – O código final
Como o legendas.tv não tem link direto para download de legendas e o usuário tem que estar logado para baixa-la, eu preferi não usar o wget para isso, pois iria aumentar a complexidade, já que teríamos que salvar o cookie num arquivo e passar pra ele como parametro para ele baixar a legenda. Então eu preferi deixar tudo com urllib mesmo com alguns tricks com headers. Este é o exemplo final e funcional:
import cookielib, urllib2,urllib
from BeautifulSoup import BeautifulSoup
import sys
import re
base_url = 'http://legendas.tv'
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)
username = raw_input('Login: ')
password = raw_input('Senha:')
login_data = urllib.urlencode({'txtLogin':username,'txtSenha':password})
request = urllib2.Request(base_url+'/login_verificar.php',login_data)
response = urllib2.urlopen(request).read()
if response.__contains__('Dados incorretos'):
print 'Ooops dados incorretos...'
raw_input('Pressione uma tecla para sair...')
sys.exit()
else:
print 'Logado com sucesso!'
print 'Fazendo busca:'
print '--------------'
busca = raw_input('Buscar por: ')
tipo = raw_input('Tipo:(1 - Release,2 - Filme, 3- Usuário):')
idioma = raw_input('Idioma: (1 - Português,2 - Ingles,99 - Todos)')
search_dict = {'txtLegenda':busca,'selTipo':tipo,'int_idioma':idioma}
search_data = urllib.urlencode(search_dict)
request = urllib2.Request(base_url+'/index.php?opcao=buscarlegenda',search_data)
response = urllib2.urlopen(request)
page = response.read()
soup = BeautifulSoup(page)
span_results = soup.find('td',{'id':'conteudodest'}).findAll('span')
i = 0
list_download_ids = []
for span in span_results:
# há dois tipos de span na busca.
#Este não contem o que nós gente queremos
if span.attrs == [('class', 'brls')]:
continue
td = span.find('td',{'class':'mais'})
#parent encontra o elemento pai.
#ex: td.parent aponta para tr e tr.parent aponta para table
release = td.parent.parent.find('span',{'class':'brls'}).contents[0]
sub_name = td.contents[0].contents[0]
downloads = td.contents[5]
comentarios = td.contents[7]
avaliacao = td.contents[10]
data = span.findAll('td')[2].contents[0]
#recupera o ID do download que tá no código javascript
download_id_js = td.parent.parent.attrs[1][1]
download_id = re.search('[a-z0-9]{32}',download_id_js).group(0)
list_download_ids.append(download_id)
i+=1
print 'Opção: '+str(i)
print 'Serie: '+sub_name
print 'Release: '+release
print 'Downloads: '+downloads
print 'Comentarios: '+comentarios
print 'Avaliacao: '+avaliacao
print 'Data: '+data
print '-------------------------------------'
download_op = int(raw_input('Qual opção deseja baixar? '))
url_request = base_url+'/info.php?d='+list_download_ids[download_op-1]+'&c=1'
request = urllib2.Request(url_request)
response = urllib2.urlopen(request)
legenda = response.read()
#eu acho que todas as legendas estão em formato rar
#mas só pro caso de eu estar errado.
fname = str(download_op)
if response.info().get('Content-Type').__contains__('rar'):
fname += '.rar'
else:
fname += '.zip'
f = open(fname,'w')
f.write(legenda)
f.close()
print 'Legenda '+fname+' salva com sucesso!'
Como o intuito desse post é mostrar como poderia ser um crawler para o legendas.tv, eu não me preocupei tanto com a estrutura do script (notem que ele repete algumas coisas algumas vezes) e não me preocupei com validação dos dados.
No próximo post eu vou dar umas dicas para melhora de performance e escalabilidade do crawler, mas provavelmente demorarei um pouco para escrever (Trabalho de Conclusão de Curso é complicado ..).
Espero que tenham gostado e gostaria de ouvir de vocês as suas impressões
See ya!
O que fazer quando seu codigo JavaScript se torna um monstro
Quando eu não conhecia jQuery e seus plugins, JavaScript era um tédio pra mim. É fato que uma biblioteca de alto nível (não só o jQuery, mas o mootools e o prototype, por exemplo) facilitam e deixam o desenvolvimento em JavaScript rápido e divertido, pois você não terá que se preocupar (muito) com detalhes de implementação de browsers.
Mas o que acontece quando, mesmo usando com alguma lib fodástica, seu código começa a ficar grande demais, complexo demais, fragmentado demais ou desorganizado demais? Isso é a minha definição de código monstro. Eis algumas dicas que podem te ajudar se você estiver passando por isso:
1 – Evite o Callback Hell
O jQuery mostra muitos exemplos usando funções anônimas como callback. De certo modo, isso é uma boa prática para situações simples, como o tratamento do evento clique de um botão:
alert('Fui clicado!');
});
Mas nem sempre isso é legal: muitas vezes você quer reaproveitar essa função para outros callbacks, como por exemplo:
$('#meuInput').blur(getClientes);
getClientes = function()
{
$.get('/clientes/get','',
function(data){
//trata os dados recebidos aqui
},'json')
}
Nesse caso, eu estou aproveitando minha função de callback getClientes para dois event listeners. Isso é bem legal, mas quando nosso código começa a crescer, nos esbarramos em outro problema: as funções globais.
2 – Evite funções globais
Isso quer dizer, ao invés disso:
$('#meuInput').blur(getClientes);
getClientes = function()
{
$.get('/clientes/get','',
function(data){
//trata os dados recebidos aqui
},'json');
};
Faça algo assim:
$('#meuInput').blur(Clientes.getClientes);
var Clientes = {
getClientes:function()
{
$.get('/clientes/get','',
function(data){
//trata os dados recebidos aqui
},'json');
}
};
Isto é, coloque suas funções dentro de objetos. Assim você evita funções globais e evita fazer código “estruturado”, passando a usar os recursos da orientação à objetos do JavaScript e deixando o mínimo de globals possível. Essa é a forma mais fácil que eu conheço de se evitar funções globais e organizar melhor seu código, mas há outras formas como o Module Pattern.
3 – O “this” aponta para diferentes lugares em diferentes contextos. Saiba como lidar com isso.
Você não está deixando mais todas as suas funções como globais, está encapsulando tudo dentro de objetos, reaproveitando código e feliz da vida quando percebe que o this não é mais this. Calma que eu explico.
Você já deve ter visto algo assim no jQuery:
$(this).html('fui clicado') ;
});
Isso faz com que, quando um link é clicado, o mesmo fique com o texto “fui clicado”. Deu pra notar que nesse caso, o this aponta para o elemento que disparou o evento. Mas olhe o seguinte exemplo:
{
texto:'Fui clicado',
init:function()
{
//this aponta para o objeto Cliente
$('a').click(this.aClickHandler);
},
aClickHandler:function()
{
//this aponta para o elemento "a" que disparou o evento
$(this).html(Cliente.texto);
}
};
Viu como “this” pode mudar de contexto? No exemplo acima, o problema de não ter o this apontando para o objeto Cliente foi facilmente contornado, mas você poderá precisar acessar o objeto pai de uma forma parecida com a this. Nesse caso, você pode passar um parâmetro para o evento usando o método bind() do jQuery, informando o contexto que ele foi chamado:
{
texto:'Fui clicado',
init:function()
{
//this aponta para o objeto Cliente
$('a').bind('click',{'self':this},this.aClickHandler);
},
aClickHandler:function(event)
{
var self = event.data.self;
//o self aponta para Cliente e o this aponta para
//o elemento "a" que disparou o evento
$(this).html(self.texto);
}
};
4 – Procure pelos patterns corretos.
Quem disse que padrões de projeto criam um vocabulário em comum na equipe não poderia ter dito algo mais pertinente: o uso de padrões faz com que o desenvolvedor foque nas funcionalidades da aplicação sem se preocupar com alguns detalhes de implementação.
O AjaxPatterns contém uma coleção bem legal e bem documentadas de patterns para Ajax e JavaScript em geral. Mas não espere ter problemas pra consultar os patterns: quanto mais se conhece sobre padrões, mais fácil é pensar em soluções de problemas e planejamento em geral.
5 – Deixe apenas um $(document).ready em todo o seu código
Eu diria isso para todos os event listeners que você possa vir a colocar, mas o $(document).ready é o mais crítico na minha opinião. No JavaScript não há sobreposição de eventos: se você colocar dos event listeners para um mesmo evento, os dois serão executados. Isso pode dar problemas, pois você pode deixar o $(docuement).ready em dois arquivos diferentes e você pode desejar que um evento execute antes de outro.
6 – Valide seu JavaScript
Browsers interpretam JavaScript de forma diferente, então é sempre bom ter uma padronização de código para que não tenhamos problemas com sintaxe em diferentes browsers. Uma ferramenta de validação bem legal é o jslint.
– EDIT–
Nem sei onde tava com a cabeça quando esqueci isso:
7 – Escreva testes!
Testes pode ser tido como documentação executável e facilita muito quando você pega um código de outro desenvolvedor que já tenha testes: é fácil de ver os modos de uso do código e você se sentirá seguro ao fazer algo e ver que os testes passam. Além do mais, você não precisa encher seu código de comentário pra explicar como ele funciona (comentar é bom, mas sem exageros);
Entretanto, testar JavaScript pode não ser tão fácil assim. É necessário mais disciplina e paciência do que é necessário para testar seu código backend, mas é algo que se adquire com o tempo.
See ya!
Criando Web crawlers em Python – Parte II
Ir para Criando Web crawlers em Python – Parte I
Ir para Criando Web crawlers em Python – Parte III
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:
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:
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)
# procura pela tabela com id=searchtable
tabela = soup.find('table',{'id':'searchtable'})
#retorna uma lista com todas as linhas (<tr>) da tabela
linhas_tabela = tabela.findAll('tr')
i=0
for linha in linhas_tabela:
i+=1
#encontra a primeira coluna (descricao) da linha
coluna_descricao = linha.find('td')
#o atributo contents contem uma lista com o conteudo da tag
nome_projeto = coluna_descricao.find('a').contents[0]
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:
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)
# procura pela tabela com id=searchtable
tabela = soup.find('table',{'id':'searchtable'})
#retorna uma lista com todas as linhas da tabela
linhas_tabela = tabela.findAll('tr')
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!
Minicurso de jQuery na Unimontes. Reserve ja a sua vaga!
Era pra eu ter postado isso aqui antes, mas ainda há tempo.
A comissão de formatura do 7º período de SI da Unimontes (a qual eu me incluo) está organizando um minicurso de 12 horas de jQuery. Serão duas turmas com 20 vagas: uma durante a semana (segundas e quartas-feira, de 19:30 às 21:30 – vagas esgotadas) e durante o sábado (8:00 – 12:00 – aproximadamente 10 vagas restantes).
O conteúdo a ser abordado será esse:



