Hoje eu vou compartilhar com vocês algo que achei interessante durante o desenvolvimento do crawler que tenho estudado, é a passagem de parâmetros que achei bem simples e uma questão nova que tenho visto que é a utilização do tesseract com python para processar textos de imagens.
Primeiro, vamos iniciar um projeto no scrapy(Caso não saiba como, leia esse artigo).
Eu iniciei da seguinte forma:
(sim, vou sacanear e colocar os comandos em imagens, quanto mais você digitar mais você aprende)
De acordo com os comandos da imagens, eu startei o projeto e criei um spider da olx, agora vamos começar o coding do spider.
Como disse em outro post, a estrutura de arquivos é a seguinte:
#:> tree olx_crawl/
olx_crawl/
├── olx_crawl
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── items.py
│ ├── pipelines.py
│ ├── settings.py
│ ├── settings.pyc
│ └── spiders
│ ├── __init__.py
│ ├── __init__.pyc
│ └── olx_spider.py
└── scrapy.cfg
Vamos editar o arquivo "olx_spider.py" que fica dentro da pasta spiders, como pode ver na árvore de diretório acima.
Inicialmente ele vem nessa estrutura:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
import scrapy | |
class OlxSpiderSpider(scrapy.Spider): | |
name = "olx_spider" | |
allowed_domains = ["olx.com.br"] | |
start_urls = ( | |
'http://www.olx.com.br/', | |
) | |
def parse(self, response): | |
pass |
Certo, nesse caso eu quero demonstrar para vocês como usar argumentos no scrapy, vamos modificar o arquivo padrão para que ele aceite argumentos passados na hora de realizar o crawler, primeiro eu entrei no site da OLX e fiz uma pesquisa qualquer na sessão de carros para ver como ele interpretava minha consulta, percebi que ele passa a query por GET da seguinte forma http://sp.olx.com.br/veiculos/carros?q=%22Golf+GTI%22 , onde "?q=" é a variável da pesquisa e o %22Golf+GTI%22 é o parâmetro que queremos pesquisar.
Desse modo vou editar o arquivo padrão e incluir um método __init__ para receber os parâmetros de pesquisa, ficando da seguinte forma:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
import scrapy | |
class OlxSpiderSpider(scrapy.Spider): | |
name = "olx_spider" | |
def __init__(self, pesquisa='', *args, **kwargs): | |
allowed_domains = ["olx.com.br"] | |
super(OlxSpiderSpider, self).__init__(*args, **kwargs) | |
self.start_urls = [ | |
'http://sp.olx.com.br/veiculos/carros?q=%s' % pesquisa | |
] | |
def parse(self, response): | |
pass |
Agora vamos fazer o teste?
![]() |
Se ficar pequena a imagem, clique nela para expandir. |
Podemos ver na imagem como foi feio o teste passando o parâmetro depois de ter editado o código, repare no comando:
Eu fiz uma pesquisa pelo termo exato(adicionando aspas duplas ele pesquisa pelas palavras exatas no titulo do anúncio, pode ver mais search tips aqui http://www.olx.com.br/search_tips.htm) de Golf GTI, por isso a pesquisa com aspas simples e aspas duplas.
Voltando ao comando, para utilizar o parâmetro basta colocar "-a" e o termo que vai pesquisar que foi inserido no código, pode ver na linha 8 do código, eu atribui a variável como "pesquisa", mas poderia ter colocado a variável como "piricutisco" e usado da seguinte forma:
#:> scrapy crawl olx_spider -a piricutisco='"Golf GTI"'
Sacaram?
- Poxa mano, mas e se minha pesquisa tiver N argumentos?
R: Poderá fazer desse modo:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
import scrapy | |
class OlxSpiderSpider(scrapy.Spider): | |
name = "olx_spider" | |
def __init__(self, pesquisa='', Goku='', *args, **kwargs): #ADD VARIÁVEL Goku | |
allowed_domains = ["olx.com.br"] | |
super(OlxSpiderSpider, self).__init__(*args, **kwargs) | |
self.start_urls = [ | |
'http://sp.olx.com.br/veiculos/carros?q=%s,%s' % (pesquisa, goku) #REPARE QUE ADD MAIS UM %s | |
] | |
def parse(self, response): | |
pass |
#:> scrapy crawl olx_spider -a piricutisco='"Golf GTI"' -a Goku="majin boo"
Note que cada vez que adiciona um parâmetro a chamada(-a) do parâmetro tem que ser usada.(Exemplo acima é meramente ilustrativo, ele não funciona na pagina da OLX)
E podemos verificar que o parâmetro foi passado corretamente no LOG, reparem na imagem abaixo:
Certo, voltando ao código original que estávamos desenvolvendo, vamos começar a implementar a inteligência do robô, nesse exemplo, como quero demonstrar o uso também do tesseract, vamos fazer o download e a salvar o telefone de contato do anunciante, caso não tenham reparado, o telefone tem duas proteções contra crawlers:
- Ele está atrás de um javascript.
- O texto do telefone está como imagem, para evitar o salvamento automático do telefone ou sei lá o que.
Mas, como somos trakinas, vamos realizar um bypass.
Primeiro, vamos entender a pagina onde vamos realizar o parser. Assim que realizamos a pesquisa ele nos leva para a pagina onde ficam os resultados, nosso robô tem que entrar em anuncio por anuncio e passar a pagina e assim por diante até acabar os resultados.
Vou usar como referência o artigo do Gileno[1] para realizar o parser das paginas da OLX, mas para entender melhor e não ter que ficar pulando de artigo p/ artigo, vou explicar como realizar o parser aqui.
Abrindo a pagina de pesquisa (estou usando o chrome), eu abri a ferramenta de desenvolvedor do chrome para analisar o código fonte, como na imagem abaixo eu procuro a tag html responsável pela lista de carros, observe:
![]() | |
|
No caso da OLX a tag que eu procurava é a div class="section_OLXad-list " que contém a listagem dos carros, agora vamos pegar a tag li class="item" que contém o anuncio do carro, dessa forma vamos montar uma consulta de xpath para adicionar no código do crawler pra quele possa realizar o parser.
//div[contains(@class,"section_OLXad-list")]//li[contains(@class,"item")]
Acho que o entendimento é bem simples, mas vou tentar explicar da forma que aprendi, caso esteja errado, por favor me corrijam.
Antes, vou explicar sobre os operadores // e / do xpath, eles são utilizados para definir a pesquisa dentro do texto HTML.
/div - usado para fazer a pesquisa da tag div dentro da raiz do html, caso não tenha entendido a questão da raiz, ela funciona da seguinte forma, o html ele tem uma estrutura, assim como uma estrutura de diretórios ou a indentação de um código, o xpath ele faz essa distinção, quando usamos apenas uma barra ele faz a procura penas no "diretório" raiz do HTML.
//div - usado para fazer a pesquisa da tag div em todo o código HTML, seguindo o exemplo acima, seria como pesquisa de forma recursiva dentro de todos os diretórios, trazendo tudo que ele encontrar com a tag div.
Ok, entendido isso, acho que fica mais fácil explicar o xpath acima, ele procura em todo o código pela tag div que contenha o parâmetro class que seja igual a "section_OLXad-list" e assim que ele achar, ele pesquisa dentro de todos esses "divs" encontrados a tag li que contenha a classe "item".
Não sei se consegui ser claro, mas qualquer dúvida que fique, postem nos comentários que vou tentar responder o quanto antes.
Vamos montar o código agora no nosso spider, apenas seguindo o que o Gileno fez, segue o código abaixo com parte do que o Gileno desenvolveu no artigo dele, a princípio esse código servirá como base para coletarmos o telefone e alguns dados a mais.
Caso queira entender como foi realizado as outras linhas de código, visite o artigo do Gileno.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
import scrapy | |
class OlxSpiderSpider(scrapy.Spider): | |
name = "olx_spider" | |
def __init__(self, pesquisa='', *args, **kwargs): | |
allowed_domains = ["olx.com.br"] | |
super(OlxSpiderSpider, self).__init__(*args, **kwargs) | |
self.start_urls = [ | |
'http://sp.olx.com.br/veiculos/carros?q=%s' % pesquisa | |
] | |
###### | |
# A parte abaixo foi adaptada do artigo | |
# http://www.gilenofilho.com.br/usando-o-scrapy-e-o-rethinkdb-para-capturar-e-armazenar-dados-imobiliarios-parte-i/ | |
###### | |
def parse(self, response): | |
items = response.xpath( | |
'//div[contains(@class,"section_OLXad-list")]//li[contains' | |
'(@class,"item")]' | |
) | |
for item in items: | |
url = item.xpath( | |
".//a[contains(@class,'OLXad-list-link')]/@href" | |
).extract_first() | |
yield scrapy.Request(url=url, callback=self.parse_detail) | |
next_page = response.xpath( | |
'//li[contains(@class,"item next")]//a/@href' | |
).extract_first() | |
if next_page: | |
self.log('Next Page: {0}'.format(next_page)) | |
yield scrapy.Request(url=next_page, callback=self.parse) | |
def parse_detail(self, response): | |
self.log(u'Veículo URL: {0}'.format(response.url)) | |
title = response.xpath('normalize-space(//h1[contains(@id,"ad_title")]//.)').extract_first() | |
self.log(u'Título: {0}'.format(title)) |
Certo, agora vamos para a parte que interessa, vou mostrar como usar o tesseract junto com o python, fazendo ele reconhecer a imagem a partir da pagina web.
No site da OLX, na pagina do anuncio, podemos ver que tem um telefone de contato como mostra na imagem abaixo:
O telefone está "protegido" por um javascript, dificultando a ação de crawlers, porém ele foi mal implementado, podemos notar no código fonte da pagina que a imagem original do telefone se encontra dentro das tags noscript, veja na imagem abaixo:
Repare que dentro da tag noscript tem a imagem original dentro da tag img, nesse caso eu implementei da seguinte forma o código para a leitura da imagem:
Dessa forma eu extraio o link da imagem original, assim consigo acessar a imagem com o telefone e usar o tesseract para transformar a imagem em texto, pode observar no código abaixo que fiz alguns imports novos e add mais 7 linhas no final do código.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
import scrapy | |
from PIL import Image | |
import requests | |
from StringIO import StringIO | |
import pytesseract | |
class OlxSpiderSpider(scrapy.Spider): | |
name = "olx_spider" | |
def __init__(self, pesquisa='', *args, **kwargs): | |
allowed_domains = ["olx.com.br"] | |
super(OlxSpiderSpider, self).__init__(*args, **kwargs) | |
self.start_urls = [ | |
'http://sp.olx.com.br/veiculos/carros?q=%s' % pesquisa | |
] | |
###### | |
# A parte abaixo foi adaptada do artigo | |
# http://www.gilenofilho.com.br/usando-o-scrapy-e-o-rethinkdb-para-capturar-e-armazenar-dados-imobiliarios-parte-i/ | |
###### | |
def parse(self, response): | |
items = response.xpath( | |
'//div[contains(@class,"section_OLXad-list")]//li[contains' | |
'(@class,"item")]' | |
) | |
for item in items: | |
url = item.xpath( | |
".//a[contains(@class,'OLXad-list-link')]/@href" | |
).extract_first() | |
yield scrapy.Request(url=url, callback=self.parse_detail) | |
next_page = response.xpath( | |
'//li[contains(@class,"item next")]//a/@href' | |
).extract_first() | |
if next_page: | |
self.log('Next Page: {0}'.format(next_page)) | |
yield scrapy.Request(url=next_page, callback=self.parse) | |
def parse_detail(self, response): | |
self.log(u'Veículo URL: {0}'.format(response.url)) | |
title = response.xpath('normalize-space(//h1[contains(@id,"ad_title")]//.)').extract_first() | |
self.log(u'Título: {0}'.format(title)) | |
link_telefone = response.xpath('//li[contains(@class,"item phone mb10px")]/noscript/img[contains(@class,"number")]/@src').extract_first() | |
print link_telefone | |
if link_telefone is not None: | |
link_telefone = "http:%s" % link_telefone | |
response = requests.get(link_telefone) | |
telefone = pytesseract.image_to_string(Image.open(StringIO(response.content))) | |
self.log(u'TELEFONE: {0}'.format(telefone)) |
Agora, vamos rodar o código para ver se está tudo OK.
Ao som de Astrix:
[1] http://www.gilenofilho.com.br/usando-o-scrapy-e-o-rethinkdb-para-capturar-e-armazenar-dados-imobiliarios-parte-i/
[2] Livro Web Scraping com Python - Capítulo 11 - Processamento de imagens e reconhecimento de texto - Pag 207
[3] https://pypi.python.org/pypi/pytesseract
Eita bixão dos python .
ResponderExcluirEsses mano tao hackudo memo ein tnc kkkkkk
ResponderExcluirO Scrapy é assíncrono por padrão, não parando os processos para esperar as respostas HTTP. Mas o `requests` é síncrono e faz o processo todo parar até que a requisição HTTP e a resposta HTTP sejam concluídas.
ResponderExcluirUma alternativa mais nativa para o Scrapy seria usar os Image Pipelines para baixar as imagens e depois emendar um novo pipeline para processar o reconhecimento de caracteres (OCR).
sempre tem uma trilha sonora! :P
ResponderExcluir