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+.