Archive for the ‘computação científica’ tag
Criando Web crawlers (distribuidos) em Python – Parte IV
Outros posts da série:
Na parte III eu mostrei como criar um simples sistema de recuperação de informações usando em áreas protegidas por senha, usando o legendas.tv como exemplo. Neste post, eu irei mostrar como criar um crawler distribuindo-o por vários computadores da sua rede (ou colocar várias instâncias rodando na mesma máquina).
Sistemas distribuídos sempre foi um dos meus assuntos favoritos à estudar. Ter à minha disposição um cluster para poder realizar alguns experimentos, ou desenvolver um software para rodar de forma distribuída sem precisar aumentar a complexidade do SO sempre me chamou atenção.
Neste post, eu vou mostrar como o PyRO (Python Remote Object) e seus artefatos funcionam e como tirar proveito deles para criar um crawler distribuído de forma bem fácil.
O PyRO tem uma arquitetura parecida com a RMI do Java e que lembra um pouco o CORBA. A diferença é que o PyRO tem uma arquitetura interna bem mais simples e a sua programação é bem menos verbosa, já que você não precisa de escrever quilos de código pra ter um exemplo funcional.
1 – Arquitetura básica
- PyRO server: responsável por instanciar o objeto e o distribuir pela rede. É importante notar que ele não instancia um novo objeto a cada requisição ou pra cada cliente: a mesma instância do objeto é utilizada sempre (à menos que você especifique o contrário).
- Rede: essa parte é obvia

- Client: consome a “instância” do objeto criado pelo server
Falando como um programador, a estrutura acima ficaria mais ou menos assim:
Código do servidor:
import Pyro.core
#objeto que será compartilhado
class SharedObject(Pyro.core.ObjBase):
def __init__(self):
Pyro.core.ObjBase.__init__(self)
def remoteMethod(self,message):
return 'Olá '+message
class Main:
def __init__(self):
Pyro.core.initServer()
daemon = Pyro.core.Daemon()
objeto = SharedObject() #cria a instância do objeto
uri = daemon.connect(SharedObject(),'obj')
print 'Porta do daemon',daemon.port
print 'URI do Objeto',uri
daemon.requestLoop()
if __name__=='__main__':
Main()
Código do cliente:
objeto = Pyro.core.getProxyForURI('PYROLOC://localhost:7766/obj')
#vc pode manipular os objetos como se eles estivessem locais
print "Servidor retornou:"+objeto.remoteMethod('Herberth')
Simples, não? Basta colocar o cliente pra rodar logo depois que o servidor for iniciado. Os objetos distribuídos com o PyRO tem somente uma característica que importante notar: você consegue compartilhar objetos normalmente, mas não consegue acessar suas propriedades diretamente. Se você precisar acessar uma propriedade, você terá que criar um getter/setter para essa propriedade (eu também achei meio feio, mas nem tudo é perfeito
.
Se você tiver o olho afiado vai perceber que à medida que mudamos de servidor precisamos dar um jeito de mudar o nosso script cliente pra atender à nova mudança. E se você tiver múltiplos servidores de objetos, terá que especificar cada um nos clientes. Isso, junto com outros motivos, podem criar complicações. Uma forma de resolver isso é com o PyRO Nameserver.
2 – PyRO Nameserver.
O principal papel do servidor de nomes é tirar a responsabilidade dos clientes saberem onde estão os seus servers. Os clientes devem ser os mais simples possível.
O servidor de nomes do PyRO na verdade são dois servidores: um servidor TCP/IP e um listener de broadcast. O listener broadcast serve para que os clientes descubram a localização do servidor de nomes e para que os servidores publiquem seus objetos.
Numa arquitetura com servidor de nomes, nosso sistema ficaria mais ou menos assim:
O nameserver deve ser iniciado antes dos servidores e antes dos clientes da sua aplicação. Para inicia-lo execute o comando python -m Pyro.naming ou execute-o diretamente digitando pyro-ns ou pyro-ns.cmd se você tiver usando Windows.
Eis um exemplo simples de sistema distribuído usando PyRO e o PyRO-ns:
Cliente:
objeto = Pyro.core.getProxyForURI('PYRONAME://obj')
print "Servidor retornou:"+objeto.remoteMethod('Herberth')
Servidor:
import Pyro.core
import Pyro.naming
class SharedObject(Pyro.core.ObjBase):
said = False
def __init__(self):
Pyro.core.ObjBase.__init__(self)
def isSaid(self):
return self.said
def remoteMethod(self,message):
self.said = True
return 'Olá '+message
class Main:
def __init__(self):
Pyro.core.initServer()
daemon = Pyro.core.Daemon()
ns = Pyro.naming.NameServerLocator().getNS()
daemon.useNameServer(ns) #publica o servidor no nameserver
objeto = SharedObject()
uri = daemon.connect(SharedObject(),'obj')
print 'Porta do daemon',daemon.port
print 'URI do Objeto',uri
daemon.requestLoop()
if __name__=='__main__':
Main()
Pouca coisa mudou no código, mas agora você não precisa especificar manualmente a localização do seu objeto: o nameserver faz o serviço pra você.
A inicialização do servidor e do cliente fica um pouco mais lenta depois que se passa a utilizar o servidor de nomes. O tempo extra é devido à busca do servidor de nomes no início da execução do cliente e do servidor.
Putz, falei demais sobre o PyRO, mas ainda tem muita coisa que eu não falei e que pode ser visto na documentação oficial. Agora vou abordar um pouco de como distribuir nosso crawler através dele.
3 – Distribuindo seu crawler
O crawler nada mais será do que um objeto distribuído. Sim, só isso ![]()
A teoria pode ser fácil, mas há algumas considerações que devem ser feitas em um crawler usando a abordagem distribuída:
- Como dividir o trabalho entre os clientes/peers de forma eficiente?
- Como assegurar que os crawlers não baixem a mesma página duas vezes?
- O que acontece com o controle de concorrência quando usamos um paradigma distribuído?
- Sobre single points of failure: se o servidor morre, os clientes não podem fazer nada. O que fazer nesses casos?
Infelizmente, eu não posso responder todos esses aspectos. Muita pesquisa baseada nessas perguntas têm sido feita e se você vai ficar impressionado se quiser aprofundar mesmo no assunto (como eu tou fazendo pro meu TCC).
A princípio, vamos fazer um crawler simples, que obtém todas as páginas de um determinado domínio, reporta as páginas baixadas e os links encontrados nas respectivas páginas. Já vimos como usar o BeautifulSoup e como criar crawlers básicos em outros posts. Aqui segue o exemplo de um pra usarmos na versão distribuída:
import urllib2
import Pyro.core
from BeautifulSoup import BeautifulSoup
class Crawl(Pyro.core.ObjBase):
base_url = "http://sourceforge.net"
queue = []
downloaded_links = {}
links_in_process = []
def __init__(self):
Pyro.core.ObjBase.__init__(self)
self.queue.extend(self.getLinksAndTitle(self.base_url)['links'])
def getLinksAndTitle(self,url):
#eliminar busca em dominios externos
if not self.isLinkInDomain(url):
return
if url.startswith("http://"):
req = urllib2.Request(url)
else:
req = urllib2.Request(self.base_url+url)
response = urllib2.urlopen(req).read()
soup = BeautifulSoup(response)
a = soup.findAll('a')
links = []
for i in a:
link = i.get('href')
if link != None and self.isLinkInDomain(link):
links.append(i.get('href'))
return {'links':links,'title':soup.find('title').text}
def getNextLinkToDownload(self):
if self.queue.__len__() == 0:
return False
link = self.queue[0]
del self.queue[0]
self.links_in_process.append(link)
return link
def reportDownloadedLink(self,link,title):
"""
Reporta um link baixado e seu respectivo titulo
"""
if link in self.links_in_process and \
not self.downloaded_links.has_key(link):
self.downloaded_links[link] = title
#tira o link da fila de processamento
del self.links_in_process[self.links_in_process.index(link)]
def addLinks(self,links):
for link in links:
if link not in self.queue and \
link not in self.downloaded_links and \
link not in self.links_in_process:
self.queue.append(link)
def isLinkInDomain(self,link):
return (link.startswith("http://") and \
link.startswith(self.base_url)) or \
not link.startswith('http://')
No meu caso, eu estou usando o SourceForge.net por que ele possui um robots.txt bem amigável e é um bom exemplo. Esse exemplo é bem simples (pra uma versão distribuída), mas serve perfeitamente pra ilustrar um crawler trabalhando de forma descentralizada.
Vamos ao server:
import Pyro.naming
from crawl import Crawl #nosso crawler
class Main:
def __init__(self):
Pyro.core.initServer()
daemon = Pyro.core.Daemon()
ns = Pyro.naming.NameServerLocator().getNS()
daemon.useNameServer(ns) #publica o servidor no nameserver
objeto = Crawl()
uri = daemon.connect(objeto,'crawl')
print 'Porta do daemon',daemon.port
print 'URI do Objeto',uri
daemon.requestLoop()
if __name__=='__main__':
Main()
Nenhuma novidade no servidor. Apenas colocamos a instância do crawler como objeto distribuído.
Agora o código do cliente:
crawl = Pyro.core.getProxyForURI('PYRONAME://crawl')
while True:
link = crawl.getNextLinkToDownload()
if not link:
break #nenhum link encontrado. fim do crawling
if link.endswith('download'): #so queremos paginas, sem baixar arquivos
continue
print 'Obtendo link: ',link
try:
result = crawl.getLinksAndTitle(link)
crawl.reportDownloadedLink(link,result['title'])
crawl.addLinks(result['links'])
except:
print 'Nao foi possivel obter o link ',link
Super simples. Faço algumas validações básicas e exibo a página atual que o cliente está buscando. Ele basicamente faz os seguintes passos:
- Pega o próximo link à baixar.
- Baixa-o.
- Reporta para o objeto que baixou o link.
- Adiciona mais links na fila de download.
Eis o sistema acima rodando com dois clientes:
Você pode perceber que alguns links podem se repetir. Eu ainda não estudei o PyRO à fundo, mas eu acredito que seja algo relacionado ao tempo sincronização dos objetos dos clientes com o servidor.
Espero que tenham gostado!
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 SciPy, NumPy 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:
- http://groups.google.it/group/scipy-user/browse_thread/thread/0eb1517095963c9a#
- http://fperez.org/py4science/index.html
- http://www.scipy.org
- http://conference.scipy.org/
- http://neopythonic.blogspot.com
==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.



