Como Python me Ajudou a Comprar um Carro
Entenda como web scraping pode deixar sua busca por um carro mais fácil e prática!
Quem me conhece sabe o quanto eu amo carros, Formula 1, automobilismo, racing games, kart, etc. E há tempos eu estava querendo comprar uma “lasanha” — expressão que significa um carro montado aos poucos e ‘com os ingredientes’ que o dono quiser — para transformar em um carro de pista. Dito isso, comecei a procurar um Ford Escort Mk4 bem cuidado, de bom gosto e que estivesse dentro do meu orçamento. Como todo bom “lasanheiro” recorri a OLX, onde há mais de 1 milhão de veículos anunciados. Depois de perder algumas horas de sono por vários dias seguidos caçando o carro ideal, decidi que era hora de usar a cabeça e automatizar essa busca, ou parte dela. Foi aí que comecei um script em Python para raspar (scrape) o portal de anúncios, pegar algumas informações básicas de cada anúncio de Escorts e mandar esses dados para o meu email. Assim, ficou mais fácil filtrar os anúncios que poderiam ser bons e, então, entrar no link para avaliar o carro. Consegui economizar algumas horas com esse código e vou mostrar abaixo minha abordagem simplória para essa raspagem.
O Início
Para começar é preciso instalar algumas bibliotecas e importá-las. As que eu usei são as seguintes:
#biblioteca que controla o navegador
from selenium import webdriver#pacote para gerenciar opcoes do navegador
from selenium.webdriver.chrome.options import Options#biblioteca para interpretar e manipular o codigo html
from bs4 import BeautifulSoup#dispensa apresentacoes
import pandas as pd
Depois disso, é necessário baixar um driver do browser que você desejar e especificar o caminho para ele. No meu caso eu utilizei um driver do Chrome. Além disso, aqui já podemos passar algumas configurações para o driver e chamá-lo. O argumento “incognito” #1 abre o navegador no modo anônimo, o que em alguns casos pode ser útil então sempre deixo sinalizado. Já o “headless” #2 faz com que o driver abra o navegador ‘nos bastidores’, ou seja, não irá abrir uma janela na sua tela. Eu prefiro deixar esse item comentado até que eu tenha definido toda a raspagem porque gosto de olhar o que o código está fazendo e assim confiro se está se comportando como deveria. Mas fica a seu critério.
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("--incognito") #1
chrome_options.add_argument("--headless") #2driver_path = 'YOUR_PATH/chromedriver'
driver = webdriver.Chrome(driver_path, options=chrome_options)
Agora, vamos dar um tempo no código e analisar o próprio site. A primeira coisa que eu analiso é a url. Geralmente entro no site e fico brincando com os filtros, com a navegação entre páginas, ordenação dos resultados, etc. E em tudo isso, vejo como a url se comporta e busco padrões. Por exemplo, no caso da OLX, quando você quer procurar por anúncios de Ford Escorts no Paraná, basta entrar nesse link https://pr.olx.com.br/autos-e-pecas/carros-vans-e-utilitarios/ford/escort. Se você quer ordenar os resultados por data ou por preço, basta inserir ?sf=1
ou ?sp=1
no final do endereço. Pode testar. Agora avance para a página 2 e perceba o que muda na url. Com isso, já temos um comportamento padrão para passarmos para o nosso driver acessar. Vamos testar aqui raspando anúncios da primeira até a quinta página. Para isso, crie uma lista com os números de 1 a 5 e coloque isso em uma variável. Uma maneira de criar essa lista facilmente, é usar a função range
.
#ao inves disso
paginas = [1,2,3,4,5]#use isso
paginas = list(range(6))
paginas = paginas[1:] #dessa maneira tiramos o 0 do inicio da lista
E agora vamos configurar a url para abrir página por página, configurando o número da página através da lista que criamos.
for pagina in paginas:
url = 'https://pr.olx.com.br/autos-e-pecas/carros-vans-e-utilitarios/ford/escort?o={}&pe=10000&sf=1'.format(pagina)
response = driver.get(url=url)
O Que Raspar
Como comentei antes, o meu objetivo era raspar informações básicas de cada anúncio. Não queria nada complexo, apenas o Título, Preço e Descrição do anúncio. Se fosse apenas o título e preço, a página de resultados que acabamos de abrir já poderia ser o suficiente. Mas no meu caso, a descrição também era importante. Geralmente o anunciante coloca na descrição informações relevantes sobre o veículo, documentos, eventuais problemas, etc. Eu queria avaliar esses quesitos para poder separar anúncios potenciais para negociar. Só que para ter a descrição completa, é preciso entrar no anúncio. E para entrar no anúncio, é necessário o endereço dele. Assim, a primeira raspagem que configurei foi dos links de cada anúncio por página. Agora, como dizer onde está o link para o driver?
Para isso, é necessário analisar o código-fonte da página. Basta clicar com o botão direito do mouse em qualquer lugar da página e selecionar “inspecionar”. Ou um atalho é a tecla F12. Fazendo isso, vai abrir uma janela o seu navegador apresentando o código da página como abaixo:
Se você não é muito expert em html (assim como eu), note que indiquei com uma flecha vermelha um botão seletor de elementos no site. Clique nesse botão e depois selecione alguma coisa na página. Logo o código desse elemento que você clicou é destacado. Essa é a melhor e mais fácil maneira que conheço para identificar onde estão os elementos que quero raspar no código-fonte. Fazendo isso no primeiro anúncio, deve ser destacado algo parecido com:
<a data-lurker-detail="list_id" data-lurker_list_id="726829833" data-lurker_is_featured="0" data-lurker_last_bump_age_secs="1643" data-lurker_list_position="0" href="https://es.olx.com.br/norte-do-espirito-santo/autos-e-pecas/carros-vans-e-utilitarios/escort-gl-1-8-ano-1998-mod-1999-cinza-4-portas-direcao-hidraulica-troco-726829833" target="_blank" title="Escort Gl 1.8 ano 1998 mod. 1999 cinza 4 portas direção hidráulica troco" class="fnmrjs-0 iZLVht">
E olha só, o nosso link está no parâmetro href
. Nós conseguimos ver fácil assim. Mas para o driver, precisamos passar um caminho. Então, se pararmos para analisar o html vamos ver que todos os anúncios estão numa mesma classe <li class=”c-1fcmfeb-2 ggOGTJ”>
. Ou seja, basta acessarmos essa classe e então o bloco de código dentro dela, que contém o link que precisamos. Contudo, como existem vários blocos de código com essa classe, temos que pegar apenas o primeiro ou quebrar o html em várias partes. Vamos decidir pela segunda opção.
#armazena o html em uma variavel
html = driver.page_source#transforma ele em um texto analisavel
soup = BeautifulSoup(html, 'html.parser')#quebra o html em partes conforme a classe que queremos
html_splited = html.split("sc-1fcmfeb-2 ggOGTJ")
A Raspagem
Agora sim podemos entrar em cada anúncio que está nesse html particionado e pegar o link que precisamos. Então antes, vamos abrir uma lista vazia #3 para preencher com as urls que vamos raspar. Depois, vamos acessar o elemento #4 que contém o link. Além disso, vamos colocar também um tratamento de exceção #5, visto que essa mesma classe também serve para identificar os anúncios patrocinados (propagandas) e isso pode ocasionar erros na raspagem. E assim pegamos o link #6 de cada anúncio e adicionamos na lista vazia #7 que criamos.
links = [] #3for anuncio in html_splited:
try: #5
soup = BeautifulSoup(anuncio, 'html.parser')
elemento_link = soup.find("a", {"data-lurker-detail":"list_id"}) #4
link = elemento_link.get("href") #6
except:
None
else:
links.append(link) #7
Dê um print(links)
e confira se deu tudo certo. E com isso feito, vamos iterar o processo de abertura de cada um desses links e vamos efetuar praticamente os mesmos passos de análise do html que fizemos anteriormente para raspar o que precisamos. No meu caso, eu quero pegar o título, preço e descrição. E a raspagem ficou mais ou menos assim.
titulos = []
precos = []
descricoes = []for u in links:
try:
response = driver.get(url=u)
html = driver.page_source
soup = BeautifulSoup(html, 'html.parser')
content = soup.find("div", {"id":"content"})
titulo = content.find("h1", {"tag":"h1"})
texto_titulo = titulo.text
preco = content.find("h2", {"tag":"h2"})
texto_preco = preco.text
descricao = content.find("span", {"class":"sc-ifAKCX sc-1sj3nln-1 eqxsIR"})
texto_descricao = descricao.text
texto_descricao = texto_descricao.replace('\n', '')
except:
titulos.append("None")
precos.append("None")
descricoes.append("None")
else:
titulos.append(texto_titulo)
precos.append(texto_preco)
descricoes.append(texto_descricao)
E pronto! Veja se tudo foi raspado corretamente. Caso não, pode ser algo relacionado a indentação. Aqui está o código completo para sanar dúvidas e com mais algumas trativas de erro.
Organização dos Dados e Envio
O primeiro passo para essa parte é criar uma matriz #8 com as listas que contém os títulos, preços e descrições, e inserir a matriz em um dataframe do pandas #9. Após, exporte esse dataframe para um arquivo .csv #10.
matrix = {
'Titulo': list(filter(None, titulos)),
'Preco': list(filter(None, precos)),
'Descricao': list(filter(None, descricoes)),
'Link': list(filter(None, links))
} #8df = pd.DataFrame(data=matrix) #9caminho_csv = '/YOUR_PATH/Anuncios.csv'export_csv = df.to_csv(caminho_csv,
index=None,
header=True,
encoding='utf-8'
) #10
E, finalmente, vamos enviar esse arquivo por email. Esse código abaixo serve para enviar emails via Gmail e é um pouco “chato”. Como não é o foco nesse artigo, basta dar uma olhada na documentação. Aliás, tudo que não ficou muito claro tecnicamente aqui (peço desculpas mas o artigo ia virar um livro) pode ser esclarecido nas documentações das bibliotecas que estamos usando.
import smtplib
import mimetypes
from email.mime.multipart import MIMEMultipart
from email import encoders
from email.message import Message
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.text import MIMETextemailfrom = "EMAIL REMETENTE"
emailto = "EMAIL DESTINATARIO"
username = "SEU EMAIL"
password = "SUA SENHA"msg = MIMEMultipart()
msg["From"] = emailfrom
msg["To"] = emailto
msg["Subject"] = "Anúncios da OLX"
msg.preamble = "Veja o anexo!"ctype, encoding = mimetypes.guess_type(caminho_csv)
if ctype is None or encoding is not None:
ctype = "application/octet-stream"maintype, subtype = ctype.split("/", 1)if maintype == "text":
fp = open(caminho_csv)
attachment = MIMEText(fp.read(), _subtype=subtype)
fp.close()
elif maintype == "image":
fp = open(caminho_csv, "rb")
attachment = MIMEImage(fp.read(), _subtype=subtype)
fp.close()
elif maintype == "audio":
fp = open(caminho_csv, "rb")
attachment = MIMEAudio(fp.read(), _subtype=subtype)
fp.close()
else:
fp = open(caminho_csv, "rb")
attachment = MIMEBase(maintype, subtype)
attachment.set_payload(fp.read())
fp.close()
encoders.encode_base64(attachment)
attachment.add_header("Content-Disposition", "attachment", filename=caminho_csv)
msg.attach(attachment)server = smtplib.SMTP("smtp.gmail.com:587")
server.starttls()
server.login(username,password)
server.sendmail(emailfrom, emailto, msg.as_string())
server.quit()
Voilà! Agora é só agendar a execução desse código todo conforme sua necessidade, analisar os dados que chegaram no sua caixa de entrada, selecionar os anúncios que parecem bons, acessar o link e negociar!
Resumo do Scraping
Tudo isso que apresentei acima são técnicas “simples” para raspagem de websites. Existem formas mais sustentáveis, com tratativas de exceção mais complexas, e maneiras mais eficazes de identificar os elementos no código-fonte. Por exemplo, quebramos o html de acordo com um texto “sc-1fcmfeb-2 ggOGTJ”
. Mas o que acontece se o código for alterado e esse texto sumir? Já era, a raspagem vai travar. Ou seja, teríamos que desenvolver uma forma de achar esse texto automaticamente antes de quebrar o html. Também existem mais parâmetros que podemos passar para o driver, em alguns casos é bom alterar o user agent e até variar o proxy. Enfim, como para esse mini-projeto isso não era necessário e tudo o que eu queria era só economizar um tempo para encontrar o meu carro, criei um código suficiente para a tarefa que tinha em mente.
Sinta-se livre para usar esse código e buscar o seu anúncio perfeito na OLX de forma mais prática e rápida. E caso queira falar mais sobre web scraping, me sugerir boas práticas ou ideias para melhorar meu código, ou ainda conversar sobre tudo que mencionei que amo no início do artigo, me encontre no LinkedIn. E se ficou curioso para saber qual carro eu comprei, veja no Instagram.
“Se tudo parece estar sob controle, você não está indo rápido o suficiente”. — Mario Andretti