Usando o Scrapy e o Rethinkdb para capturar e armazenar dados imobiliários - Parte I
Publicado em: . | Por: Gileno Filho | Arquivado em: tutoriais
Introdução
Olá pessoal, a algum tempo que estou organizando minha agenda para voltar a trabalhar em alguns Web Crawlers para capturar dados imobiliários que é um tipo de dado que gosto de trabalhar. Assim resolvi publicar esse artigo, que fará parte de uma série de 3, onde irei mostrar como fazer um crawler usando o Scrapy e como armazenar num banco de dados que muito me interessa, o rethinkdb.
Agenda
- Parte I - Configurando e rodando o Scrapy
- Parte II - Instalando, configurando e armazenando os dados no Rethinkdb
- Parte III - Deploy do projeto Scrapy
Vamos lá
O Scrapy é um framework para facilitar o desenvolvimento de crawlers, pois mesmo sendo fácil fazer um crawler usando o built-in do Python como o urllib, ou bibliotecas externas como requests, fica mais rápido e prático utilizar um conjunto de ferramentas desenhados para este fim, é o famoso "batteries included".
Para instalar o Scrapy vamos utilizar o pip (supondo que já esteja num virtualenv):
pip install scrapy
Após instalar o scrapy você terá disponível o comando "scrapy" mais detalhes sobre as opções dele aqui:
http://doc.scrapy.org/en/1.0/topics/commands.html
O scrapy é composto de "spiders" que é a parte do código que irá definir que páginas serão acessadas para capturar informações, "items" que são as informações que serão extraídas das páginas e os "pipelines" que irão definir como esses "items" serão processados.
Um exemplo simples de um crawler utilizando o Scrapy:
Para rodar basta utilizar o comando scrapy:
scrapy runspider gilenofilho.py
Você verá algo assim:
...
2015-06-22 18:26:07 [scrapy] DEBUG: Redirecting (301) to <GET http://gilenofilho.com.br/> from <GET http://www.gilenofilho.com.br/>
2015-06-22 18:26:07 [scrapy] DEBUG: Crawled (200) <GET http://gilenofilho.com.br/> (referer: None)
2015-06-22 18:26:07 [gilenofilho] DEBUG: Hello World: http://gilenofilho.com.br/
2015-06-22 18:26:07 [scrapy] INFO: Closing spider (finished)
2015-06-22 18:26:07 [scrapy] INFO: Dumping Scrapy stats:
...
Uma Spider é basicamente uma classe que herda de scrapy.Spider
o atributo start_urls
está definindo as urls que devem ser acessadas inicialmente, você pode definir um método starts_requests
que deve retornar uma lista de scrapy.Request
através da keyword yield
, assim:
Obs: a implementação padrão do starts_requests
procura o atributo start_urls
e chama o método parse
.
Deve se utilizar yield
e passar um callback para a scrapy.Request
pois o scrapy faz requisições assíncronas, isto é, não espera a requisição inicial acabar para fazer uma nova requisição, e desta forma utiliza o conceito de geradores em Python, você pode ver mais detalhes de como geradores funcionam acessando as palestras de Luciano Ramalho em: http://pt.slideshare.net/ramalho/.
O scrapy tem o conceito de projeto onde ficam organizados as "Spiders", "items" e "pipelines". Para criar um projeto scrapy basta utilizar o comando:
scrapy startproject nome_do_projeto
Você verá que uma estrutura de diretórios e arquivos será criada para que facilite o crescimento do seu crawler. Eu criei um projeto chamado scrapy_olx e meu sistema de diretórios ficou assim:
scrapy_olx
- scrapy_olx
- - spiders
- - - __init__.py
- - __init__.py
- - items.py
- - pipelines.py
- - settings.py
- scrapy.cfg
Agora vamos criar nosso primeiro Spider, com o comando:
scrapy genspider olx pe.olx.com.br
Estou criando um crawler para capturar os imóveis de Pernambuco (PE) e esta é a url inicial no OLX. Dentro do diretório spiders foi criado um arquivo chamado olx.py contendo o esqueleto do nosso código, vou modificar o starts_urls para começar com a url dos imóveis para alugar, o arquivo modificado vai ficar assim:
Para rodar este código basta utilizar o comando:
scrapy crawl olx
Se antes usamos a opção runspider
agora que estamos num projeto scrapy usamos a opção crawl
passando o nome da spider, no caso olx.
Agora vamos modificar o método parse
para adicionar a lógica de verificar os links para os imóveis que a página contém, após isso iremos acessar a página do imóvel para só então capturar os dados do imóvel.
Agora no método parse usamos xpath para percorrer a estrutura do html e encontrar todos os li que contém a class css item, cada elemento deste tipo irá ter um imóvel e assim fazemos um for para buscar o href do link único para o imóvel. Ao pegar o link de cada imóvel retornamos através do yield uma nova requisição para o scrapy realizar e passamos desta vez outro callback, o parse_detail
. Nesse primeiro momento ele apenas vai fazer o log da url.
Rodando novamente o código será possível que o scrapy irá realizar 51 requisições, 1 para cada imóvel - 50 por página - e a primeira para acessar a página inicial.
scrapy crawl olx
Após fazer a lógica de acessar cada imóvel vamos adicionar a parte de verificar se existe uma próxima página, visto que são exibidos 50 imóveis por página mas no final da página há uma paginação.
O código ficará assim:
Obs: Atualizei o código com a dica do Elias Dorneles sobre o normalize-path, essa função do xpath serve para remover o excesso de caracteres em branco e outros caracteres que a renderização do html ignora mas que está presente no código fonte.
Veja que após o for
usamos novamente o xpath para encontrar o link que contém o texto Próxima página que está no final da página do OLX - basta examinar o html. Se houver o link mandamos outra requisição ao scrapy, só que desta vez o callback é o mesmo método que estamos, o parse
. Para acrescentar fazemos o log do título do imóvel, dando uma ideia de como podemos pegar outras informações sobre o imóvel, mais detalhes sobre isso no próximo artigo.
Para rodar, basta novamente enviar o comando:
scrapy crawl olx
Para perceber que o scrapy é assíncrono nas suas requisições, basta ver que ele acessar a próxima página antes de acabar as requisições dos imóveis da primeira página.
Enfim, essa primeira parte fica por aqui, na próxima irei mostrar os conceitos de Item e Pipeline que o scrapy possui para capturar e processar os dados armazenados.