Herberth Amaral Software development adventures

7Mar/103

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!

  • Share/Bookmark
27Feb/102

Criando Web crawlers em Python – Parte I

Ir para: Criando Web Crawlers em Python - Parte II

Iniciemos com a definição de Web crawler:

Um Web crawler é um programa de computador que navega pela World Wide Web de maneira metódica e automatizada. [Wikipedia]

Os Web crawlers (ou simplesmente crawlers) têm um papel importante na nossa vida hoje. Como exemplos clássicos de Web crawlers, podemos citar o Google Bot, o Yahoo! Slurp e o MsnBot. Sem esses crawlers, não teríamos a busca do Google, do Yahoo! ou do Bing.

O objetivo dessa série de tutoriais não é ensinar criar crawlers de alta disponibilidade, alta performance e distribuídos mas mostrar como eles podem ser construidos sem muito custo ou complexidade, de forma que atenda uma necessidade específica. Entenda necessidade específica como download de imagens de um conjunto de páginas, um sistema de download de arquivos de hosts como o Rapidshare ou MegaUpload (sim, é possível!) ou uma busca e categorização de informações distribuídas entre diversos sites (como editais de concursos em portais).

Aspectos legais e ética de crawlers:

Antes de continuar lendo esta série, gostaria de informar que não é todo site que lhe dá permissão de acesso por meios de robores de busca. Procure sempre ler as políticas de uso e sempre respeite o robots.txt: se não é pra você crawlear determinada página, não o faça.

O processo:

Esquema básico de funcionamento de um Web Crawler

Esquema básico de funcionamento de um Web Crawler

Preste atenção aos componentes do crawler:

  1. Fila: pode conter uma ou mais URLs. Dependendo do tamanho da fila, a mesma pode estar no disco, ao invés de somente na memória.
  2. Downloader: geralmente costuma-se colocar um downloader multithreaded. No nosso primero exemplo vamos fazer um downloader singlethreaded para evitar complexidade.
  3. Parser: esse é o mais interessante. Ele vai extrair informações da página baixada pelo downloader segundo as especificações passadas pra ele. É pra ele que você irá dizer que quer extrair todas as imagens de uma determinada página.
  4. Storage: pode ser qualquer tipo de armazenamento em qualquer tipo de mídia. Não iremos persistir os dados nessa primeira parte do tutorial.

Nota-se que esse diagrama não representa um crawler de alta performance, pois ele mal diz sobre a arquitetura do mesmo, não diz sobre hardware, links de rede, paralelismo e tudo mais, mas serve com o intuito de explicar o básico do sistema.

Maos à obra: fazendo um downloader com uma fila simples

Para download de páginas e arquivos na Web, nós usaremos o urlib e a urlib2 do Python. Apesar das versões das libs, uma complementa a outra. Por exemplo, só se pode mudar os headers HTTP na urllib2 e o método urlencode só está disponível na urllib. Contudo, nós usaremos mais a urllib2. O código:

import urllib2

url = "http://www.google.com"

request = urllib2.Request(url)
response = urllib2.urlopen(request)
document = response.read()
print document

Execute esse código e se tudo der certo você verá o código fonte da página inicial do Google!

Bem, só ter o código fonte da página não é muito útil. Precisamos extrair informações dela. Uma forma de que podemos adotar é por Regex (expressão regular), mas usar regex dificultaria nosso trabalho e fugiríamos da idéia de deixar as coisas simples.

Um grande problema da Web hoje é que muitos desenvolvedores escrevem código (X)HTML mal formado e isso dificulta um pouco nossa vida. Felizmente isso não é um impedimento grave e podemos contorna-lo com o BeautifulSoup. O BeautifulSoup faz um excelente trabalho transformando o código de marcação (HTML, XML ou XHTML) em objetos que podem ser facilmente manipulados. De acordo com o esquema mostrado apresentado aqui, o BeautifulSoup faz parte do parser.

Façamos algo útil: vamos listar todos os links da página inicial do Google.

import urllib2
from BeautifulSoup import BeautifulSoup

url = "http://www.google.com"

request = urllib2.Request(url)
response = urllib2.urlopen(request)
document = response.read()
soup = BeautifulSoup(document) #normaliza o documento para que o mesmo seja acessível via objetos

links = soup.findAll('a')  # retorna uma lista com todos os links do documento

for link in links:
    print link

Fácil não?!

Se tudo correr bem, você deve ver uma saída igual a essa:

<a href="http://images.google.com.br/imghp?hl=pt-BR&amp;tab=wi">Imagens</a>
<a href="http://video.google.com.br/?hl=pt-BR&amp;tab=wv">Vídeos</a>
<a href="http://maps.google.com.br/maps?hl=pt-BR&amp;tab=wl">Mapas</a>
<a href="http://news.google.com.br/nwshp?hl=pt-BR&amp;tab=wn">Notícias</a>
<a href="http://www.orkut.com/Home.aspx?hl=pt-BR&amp;tab=w0">Orkut</a>
<a href="http://mail.google.com/mail/?hl=pt-BR&amp;tab=wm">Gmail</a>
<a href="http://www.google.com.br/intl/pt-BR/options/" style="text-decoration:none"><u>mais</u> &raquo;</a>
<a href="/url?sa=p&amp;pref=ig&amp;pval=3&amp;q=http://www.google.com.br/ig%3Fhl%3Dpt-BR%26source%3Diglk&amp;usg=AFQjCNEufhwNAC9POZqcS5r7r07CUPbvAA">iGoogle</a>
<a href="/preferences?hl=pt-BR">Configurações da pesquisa</a>
<a href="https://www.google.com/accounts/Login?hl=pt-BR&amp;continue=http://www.google.com.br/">Fazer login</a>
<a href="/search?q=Vancouver+2010&amp;ct=olympics10-sskating-hp&amp;oi=ddle"><img src="/logos/olympics10-sskating-hp.png" width="523" height="170" border="0" alt="Vancouver 2010" title="Vancouver 2010" id="logo" onload="window.lol&amp;&amp;lol()" /></a>
<a href="/advanced_search?hl=pt-BR">Pesquisa avançada</a>
<a href="/language_tools?hl=pt-BR">Ferramentas de idiomas</a>
<a href="/intl/pt-BR/ads/">Soluções de publicidade</a>
<a href="/services/">Soluções empresariais</a>
<a href="/intl/pt-BR/about.html">Tudo sobre o Google</a>
<a href="http://www.google.com/ncr">Google.com in English</a>
<a href="/intl/pt-BR/privacy.html">Privacidade</a>
<a href="http://images.google.com.br/imghp?hl=pt-BR&amp;tab=wi">Imagens</a><a href="http://video.google.com.br/?hl=pt-BR&amp;tab=wv">Vídeos</a><a href="http://maps.google.com.br/maps?hl=pt-BR&amp;tab=wl">Mapas</a><a href="http://news.google.com.br/nwshp?hl=pt-BR&amp;tab=wn">Notícias</a><a href="http://www.orkut.com/Home.aspx?hl=pt-BR&amp;tab=w0">Orkut</a><a href="http://mail.google.com/mail/?hl=pt-BR&amp;tab=wm">Gmail</a><a href="http://www.google.com.br/intl/pt-BR/options/" style="text-decoration:none"><u>mais</u> &raquo;</a><a href="/url?sa=p&amp;pref=ig&amp;pval=3&amp;q=http://www.google.com.br/ig%3Fhl%3Dpt-BR%26source%3Diglk&amp;usg=AFQjCNEufhwNAC9POZqcS5r7r07CUPbvAA">iGoogle</a><a href="/preferences?hl=pt-BR">Configurações da pesquisa</a><a href="https://www.google.com/accounts/Login?hl=pt-BR&amp;continue=http://www.google.com.br/">Fazer login</a><a href="/search?q=Vancouver+2010&amp;ct=olympics10-sskating-hp&amp;oi=ddle"><img src="/logos/olympics10-sskating-hp.png" width="523" height="170" border="0" alt="Vancouver 2010" title="Vancouver 2010" id="logo" onload="window.lol&amp;&amp;lol()" /></a><a href="/advanced_search?hl=pt-BR">Pesquisa avançada</a><a href="/language_tools?hl=pt-BR">Ferramentas de idiomas</a><a href="/intl/pt-BR/ads/">Soluções de publicidade</a><a href="/services/">Soluções empresariais</a><a href="/intl/pt-BR/about.html">Tudo sobre o Google</a><a href="http://www.google.com/ncr">Google.com in English</a><a href="/intl/pt-BR/privacy.html">Privacidade</a>

Na verdade, a linha

links = soup.findAll('a')

não retorna uma lista de strings, mas uma lista de objetos que representam tags em HTML (classe BeautifulSoup.Tag). Logo, há outras propriedades e métodos que podemos explorar. Digamos que você só queira as URLs dos links e não está interessado no texto ou na marcação. O nosso código ficaria assim:

import urllib2
from BeautifulSoup import BeautifulSoup

url = "http://www.google.com"

request = urllib2.Request(url)
response = urllib2.urlopen(request)
document = response.read()
soup = BeautifulSoup(document) #normaliza o documento para que o mesmo seja acessível via objetos

links = soup.findAll('a')  # retorna uma lista com todos os links do documento

for link in links:
    print link['href']

Como saída, você pode obter algo do tipo:

http://images.google.com.br/imghp?hl=pt-BR&tab=wi

http://video.google.com.br/?hl=pt-BR&tab=wv

http://maps.google.com.br/maps?hl=pt-BR&tab=wl

http://news.google.com.br/nwshp?hl=pt-BR&tab=wn

http://www.orkut.com/Home.aspx?hl=pt-BR&tab=w0

http://mail.google.com/mail/?hl=pt-BR&tab=wm

http://www.google.com.br/intl/pt-BR/options/

/url?sa=p&pref=ig&pval=3&q=http://www.google.com.br/ig%3Fhl%3Dpt-BR%26source%3Diglk&usg=AFQjCNEufhwNAC9POZqcS5r7r07CUPbvAA
/preferences?hl=pt-BR

https://www.google.com/accounts/Login?hl=pt-BR&continue=http://www.google.com.br/

/search?q=Vancouver+2010&ct=olympics10-sskating-hp&oi=ddle
/advanced_search?hl=pt-BR
/language_tools?hl=pt-BR
/intl/pt-BR/ads/
/services/
/intl/pt-BR/about.html

http://www.google.com/ncr

/intl/pt-BR/privacy.html

Como você pode ver, o BeautifulSoup é quase uma mãe quando o assunto é extrair informações de HTML :P .

No próximo tutorial, eu abordarei usos mais avançados do BeautifulSoup e mostrarei como usar os métodos GET e POST e também mostrarei uma pequena aplicação para procurar e baixar arquivos do SourceForge.net. Até o próximo!

Ir para: Criando Web Crawlers em Python - Parte II

  • Share/Bookmark
Tagged as: , , 2 Comments
29Nov/093

Python e computacao cientifica

Há algum tempo eu venho notando algumas aplicações desenvolvidas em Python e desde então eu venho me perguntando: será mesmo possível utilizar Python, de forma eficiente, no meio científico? Bem, minha pesquisa me leva a crer que sim, absolutamente.

O Guido Van Rossum, criador e atual mantenedor da linguagem, postou recentemente em seu blog sobre a Py4Science, um workshop que visou demonstrar como o Python está sendo útil na prática em pesquisas científicas.

Eu sabia que Python era perfeitamente aplicável à pesquisas científicas, seja pelo poder da linguagem ou pelas bibliotecas e frameworks que ela provê para facilitar o desenvolvimento de aplicações científicas, mais notavelmente o SciPyNumPy e o MatPlotLib. O me impressionou foi ver o nível dos projetos envolvidos:

  • O time do Telescópio Hubble utiliza Python há mais de 10 anos
  • O pessoal da Universidade de Washington está trabalhando no SAGE, um software com o objetivo de servir de alternativa viável ao Matlab, Mathematica, Maple e Magma.
  • Nipype, software para criação de interfaces de softwares de neuroimagem.
  • SymPy, biblioteca para matemática simbólica, escrita em Python puro.
  • Um projeto de $1 bilhão do governo indiano para melhorar a educação na Índia. Inclui o Departamento de Engenharia Aeroespacial em IIT Bombay na educação de Ciência e Engenharia. Mais em http://fossee.in/
  • Biopython, conjunto de ferramentas para computação biológica.
  • Vários outros.

Recentemente, eu vi uma discussão no Google Groups em que o autor pergunta por que o SciPy é melhor que o Matlab. Tá certo que uma pergunta dessas é bem tendenciosa (um amigo disse que aconteceria o mesmo na lista do Matlab), mas eu li toda a discussão e vi coisas bastante interessante, principalmente quando dizem sobre o f2py. Para quem tem código legado em Fortran, quer mudar e tá com qualquer dificuldade, essa é uma ótima oportunidade.

Algo que eu não posso deixar de falar é da Python in Science Conference (SciPy Conference), uma super conferência anual sobre SciPy e Python em computação científica que acontece no Caltech, Califórnia. Ainda não vi nenhum trabalho brasileiro lá. Já é hora, hein?

Eu sou longe de ser um cientista de renome para falar como o Python tem me ajudado em minhas pesquisas científicas. Mas, como acadêmico, eu posso dizer que serve perfeitamente para meus trabalhos universitários e eu percebo que a vida para outros acadêmicos poderia ser mais simples se eles utilizassem Python. Somente o fato de não precisar lidar com ponteiros ou gerenciamentos de baixo nível atrai algumas pessoas. Outra coisa importante: Python é livre e gratuito. Você não precisa piratear ou comprar uma cópia cara como algumas pessoas fazem com o Matlab e outros softwares ;)

Links úteis:

==EDIT==

Por algum motivo ainda desconhecido, o título não está aparecendo com os devidos acentos. Irei alterar quando eu souber o que está acontecendo.

  • Share/Bookmark
25Nov/091

Hello World de Python + OpenGL

Há quase um ano eu comecei a estudar Python e gostei de muitas características da linguagem, como o fato de ser de altíssimo nível (very high level), sintaxe clara e elegante, poderosa, portável,  super rápida,fácil aprendizado e um dos melhores: tipagem forte e dinâmica.

Gostei tanto que comecei a fazer alguns trabalhos de faculdade (mais notavelmente trabalhos nas áreas de Banco de Dados, Inteligência Artificial e, recentemente, Computação Gráfica) usando Python. Para vários trabalhos eu encontrei snippets de código, mas não foi tão fácil para CG usando OpenGL. Por isso, resolvi postar esse pequeno exemplo de como usar Python (na versão 2.6.4) + OpenGL.

Primeiro, baixe o PyOpenGL em http://pypi.python.org/pypi/PyOpenGL, descompacte e execute os comandos para instalar o pacote:´

python setup.py build
python setup.py install

Lembrando que se você estiver no Windows, o executável do Python deverá estar no seu PATH.

Instalado o pacote, é hora de fazer um teste. Abra o shell do Python e digite isso:

>>> import OpenGL

Se não aparecer nenhuma mensagem de erro, o pacote do OpenGL foi instalado com sucesso. Agora é hora de uma pequena demonstração de uso do PyOpenGL:

from OpenGL.GL import *
from OpenGL.GLUT import *

def display():
  glClear (GL_COLOR_BUFFER_BIT)
  glBegin (GL_POLYGON)
  glVertex2f(0.4,0.4)
  glVertex2f(0.01,0.01)
  glVertex2f(0.3,0.6)
  glEnd()
  glFlush()
glutInit ()
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB)
glutInitWindowSize (250,250)
glutInitWindowPosition (0,0)
glutCreateWindow ("Testando o PyGraphs")
glClearColor (0.0, 0.0, 0.0, 0.0)
glOrtho (0.0, 1.0, 0.0, 1.0, -1.0, 1.0)
glutDisplayFunc(display)
glutMainLoop ()

A API do OpenGL pro Python segue as mesmas convenções da API para C/C++. Então, todo e qualquer exemplo que você achar na Web em C/C++ é compatível com o Python (as vezes com algumas pequenas alterações de tipos, mas o nome das funções continua o mesmo).

Se tudo estiver ok, você deverá ver algo parecido com a seguinte figura:

pygraph

Alguns links úteis:

Acho que é isso. Até a próxima!

  • Share/Bookmark