sábado, 18 de janeiro de 2014

Scrapy - bem facinho


Sempre algum amigo meu me pergunta sobre como extrair informações de sites. Os objetivos são os mais diversos: obter lista de preços de sites concorrentes, conseguir telefones ou email e um monte de outros interesses. Dão nomes até engraçados aos programas/scripts que desejam. Já escutei "Chupa cabra", "Chupizque" e por ai vai. Sempre fiz scripts para "Chupinhar" informações em sites em Perl e até em Python. Mas recentemente tenho utilizado o Scrapy. O objetivo desse post e ajudar meus amigos e quem quiser a iniciar com o Scrapy. De fato, ele é muito fácil de usar.



O que é o Scrapy?

O Scrapy é um webcrawler muito rápido escrito e programável em Python. As características do Scrapy que mais chamam a atenção são:
  1. simplicidade - ele é muito simples de usar e automatiza várias tarefas comuns neste tipo de trabalho;
  2. produtividade - nunca tive a experiência de usar um webcrawler onde tão rapidamente eu obtivesse algum resultado;
  3. velocidade - provavelmente por utilizar o Python Twisted, que toma conta do IO de forma assíncrona, o Scrapy é muito rápido

Instalação

Se você esta utilizando Linux, de forma geral, não é interessante utilizar os sistemas de pacotes nativos. Nestes casos o Scrapy costumam estar em versões antigas. O comum é utilizarmos o Python pip.

Instale o Scrapy com o comando:
pip install Scrapy

Nosso alvo

Screenshot do www.citebem.com.br

Pra brincadeira ficar séria, vamos criar um projeto e coloca-lo pra funcionar. O site escolhido foi o Citebem(http://www.citebem.com.br). Esse é um site de frases famosas separadas por autores e assuntos. Iremos extrair todas as frases desse site.

Criando o projeto

Para iniciar nosso projeto, basta um comando que o scrapy já cria um modelo básico que podemos completar.
scrapy startproject citebem
Isto cria um diretório chamado "citebem". Este será nosso diretório de trabalho. Assim:
cd citebem
Para ver o conteúdo desse diretório, digite:
find .

 A saída será algo como:
.
./citebem
./citebem/spiders
./citebem/spiders/__init__.py
./citebem/pipelines.py
./citebem/items.py
./citebem/__init__.py
./citebem/settings.py
./scrapy.cfg

Criando um spider

Agora devemos criar um spider. Esse é o objeto que irá, de verdade, navegar no site alvo. A tarefa também é muito simples, basta dar o comando:
scrapy genspider c1 citebem.com.br
Isto irá criar um novo arquivo chamado "c1.py" no dirétorio "citebem/spiders/". O parâmetro "c1" diz ao comando o nome da classe e arquivos que serão criados. O  parâmetro "citebem.com.br" diz qual domínio é nosso alvo.

Localizando as frases

Chegou a hora da gente saber melhor o que quer. Navegando no site alvo, vemos que cada frase está em uma página com o seguinte esquema de url: "/frases/frase/frases-[Algo que identifica a frase]". Vemos, também, que em todas as frases temos o seu texto e o nome do seu autor.

Página com frase

Completando Item .py 

A Classe "Item" tem um papel importante no Scrapy. Ela é o nosso gabarito e deve descrever os dados que queremos extrair. Quando criada o arquivo items.py tem o seguinte conteúdo:
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html

from scrapy.item import Item, Field

class CitebemItem(Item):
    # define the fields for your item here like:
    # name = Field()
    pass
Alteraremos para:
from scrapy.item import Item, Field

class CitebemItem(Item):
    frase = Field()
    autor = Field()
Isso mostrar que estamos interessados no conteudo da frase e no seu autor.

XPath no Chrome

Depois de conhecer as páginas de onde as informações serão extraidas e de definir quais informações queremos, agora é hora de aprender a encontra-las dentro das páginas.

O Scrapy utiliza seletores de conteúdo em XPath. A idéia do XPath é endereçar o conteúdo. O melhor da brincadeira é que nem precisamos aprender muita coisa de XPath. A dica é utilizar o Google Chrome.

Dentro do Google Chrome, em uma página que queremos obter os conteudos devemos seguir alguns passos:
  1. clique com o botão direito do mouse sobre o conteudo que queremos o endereço XPath;
  2. no menu que aparece, clique em "Inspect Element";
  3. Ira aparecer o informações mostrando a estrutura da página;
    Inspect mostrando estrutura da página
  4. Localize novamente o conteúdo que desejamos (no caso, a frase);
  5. clique com o botão direito sobre o conteúdo dentro da estrutura da página exibida;
  6. agora clique em "Copy XPath"
  7. pronto o seletor/endereço XPath está na sua área de transferência e basta um Ctrl-v para resgatá-lo.
No nosso caso a frase esta em "//*[@id="main"]/div[1]/div[2]/blockquote/p".

Testando XPath no Scrapy Shell

O Scrapy fornece mais uma ferramenta muito prática, uma shell. Para usar devemos digitar:
scrapy shell http://www.citebem.com.br/frases/frase/frases-550
Agora podemos testar nossos seletores XPath. Abaixo segue uma sessão nessa shell:
>>> sel.xpath('//*[@id="main"]/div[1]/div[2]/blockquote/p')
[A vida \xe9 maravilhosa se n\xe3o se tem me'>]
>>> sel.xpath('//*[@id="main"]/div[1]/div[2]/blockquote/p').extract()
[u'A vida \xe9 maravilhosa se n\xe3o se tem medo dela.
'] >>> sel.xpath('//*[@id="main"]/div[1]/div[2]/blockquote/p/text()').extract() [u'A vida \xe9 maravilhosa se n\xe3o se tem medo dela.'] >>> sel.xpath('//*[@id="main"]/div[1]/div[2]/blockquote/p/text()').extract()[0] u'A vida \xe9 maravilhosa se n\xe3o se tem medo dela.'
 
Pronto, agora temos a linha de código python que obtem a frase de uma página.

Codificando o spider

Temos agora que alterar o c1.py.
from scrapy.selector import Selector
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from citebem.items import CitebemItem

class C1Spider(CrawlSpider):
    name = 'c1'
    allowed_domains = ['citebem.com.br']
    start_urls = ['http://www.citebem.com.br/']

    rules = (
        Rule(SgmlLinkExtractor(allow=r'frases/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        sel = Selector(response)
        i = CitebemItem()
        i['frase'] = sel.xpath('//*[@id="main"]/div[1]/div[2]/blockquote/p/text()').extract()[0]
        i['autor'] = sel.xpath('//*[@id="main"]/div[1]/div[2]/blockquote/small/a/text()').extract()[0]        return i

Os dois trechos de códigos destacados, dizem:
  1. A regra para o spider, nesta regra é dito que as URLs que casam com "frases/" devem ser seguidas e processadas com o método "parse_item"
  2. O objeto CitebemItem é preenchido com os conteudos extraídos da página.
Agora podemos testar nosso spider.
scrapy crawl c1
Esta linha ordena ao scrapy que execute o spider c1. Uma saída extensa deve ser mostrada. Nas linhas finais vemos um relátorio com estatísticas da execução. Um informação deve chamar a atenção "'item_scraped_count': 2,". Somente duas frases são extraidas do site. Algo ainda não está funcionando como queremos.

Colocando regras pra autores

O Scrapy é muito esperto e pode seguir os links encontrados nas páginas buscadas para percorrer um site. Mas precisamos informar como faze-lo. A única regra que usamos diz pra seguir URLs que casam com "/frases" e extrair o conteúdo delas com o método "parse_item". Se olharmos para a página principal do CiteBem, de fato, ela só tem dois links que casam com a regra. A solução seria criar uma nova regra. O código do c1 ficará assim:
from scrapy.selector import Selector
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from citebem.items import CitebemItem

class C1Spider(CrawlSpider):
    name = 'c1'
    allowed_domains = ['citebem.com.br']
    start_urls = ['http://www.citebem.com.br/']

    rules = (
        Rule(SgmlLinkExtractor(allow=r'frases/'), callback='parse_item', follow=True),
        Rule(SgmlLinkExtractor(allow=r'autores/'), follow=True),
    )

    def parse_item(self, response):
        sel = Selector(response)
        i = CitebemItem()
        i['frase'] = sel.xpath('//*[@id="main"]/div[1]/div[2]/blockquote/p/text()').extract()[0]
        i['autor'] = sel.xpath('//*[@id="main"]/div[1]/div[2]/blockquote/small/a/text()').extract()[0]
        return i

A nova regra diz pra seguir todos os links que casam com "autores/", mas não deve processar as páginas nestas URLs.

Testando novamente o spider, vemos que as frases estarão sendo processadas corretamente.

Extraindo CSV

Pra fechar com chave de ouro vamos utilizar o que fizemos para criar um arquivo .cvs contendo as frases extraídas. Basta alterar um pouca o último comando utilizado. Digite:
scrapy crawl c1 -o frases.csv -t csv

Espero que estas dicas sejam úteis. T+.


4 comentários:

  1. Otima postagem. Tenho duas duvidas, uma é como fazer para navegar entre paginas, sendo que cada pagina tem um formulario, e a proxima pagina é renderizada baseada nas informações enviadas via post na pagina anterior, e como fazer com que o scrapy processe o javascript da pagina

    ResponderExcluir
  2. Fabio, eu nunca precisei de utilizar este tipo de navegação. Para login simples a documentação do Scrapy fala do método: start_requests(http://doc.scrapy.org/en/latest/topics/spiders.html?highlight=start_requests#scrapy.spider.Spider.start_requests). Para roteiros de navegação mais elaborados, creio que a melhor solução é a codificação manual.

    Quanto ao javascript, talvez uma solução seja escrever o crawler em JS mesmo. Já utilizei esta técnica. No caso, as páginas eram abertas em um iframe e processadas. Utilizei um browser customizado, por mim mesmo, que me permitia escrever em arquivo via JS para salvar as informações.

    ResponderExcluir
  3. No momento minha solução foi utilizar um framework de testes de interface web, no caso o splinter[1] da cobrateam + xvfb[2].

    Estava querendo algo mais robusto que essa solução.

    [1] http://splinter.cobrateam.info
    [2] http://jeveaux.com/2008/xvfb-como-usar-o-selenium-sem-ter-um-x-server/

    ResponderExcluir