Como fazer webscraping em sites de venda e aluguel de imóveis programando um robô em Python?
É comum encontrar na web vários projetos de data science que utilizam modelos complexos de Machine Learn muitas vezes com acurácia próxima dos 95%. Entretanto em problemas do mundo real nem sempre os dados estão organizados num arquivo .csv pronto pra ser manipulado. Muitas vezes precisamos ir buscar os dados brutos na fonte.
Quando falamos em extrair dados de sites na web não há uma receita pronta, meu caro Watson. Cada caso é um caso e dependendo do problema você poderá precisar de uma ou outra ferramenta. Agora, imagine você a quantidade de dados disponíveis na web em blogs, sites, e-comerces e etc, prontos para serem extraídos e explorados! Neste tutorial eu vou mostrar pra você um exemplo de como coletar dados de um site de aluguel de imóveis e armazená-los em uma planilha do Calc (ou Excel).
…
Vamos lá?!
Preparando o ambiente (Opcional)
Você pode começar criando um ambiente virtual no seu computador utilizando o seguinte comando
pip install virtualenv
Feito isso, abra o terminal dentro da sua página de projeto e inicie o ambiente com o seguinte comando
virtualenv -p python3 nome_do_ambiente
Em seguida, para entrar basta usar o comando
. nome_do_ambiente/bin/activate
Pronto! Agora que você está dentro do ambiente virtual já pode instalar as bibliotecas necessárias
pip install requests_html
pip install html5lib
pip install bs4
Essa etapa é opcional. Se você estiver usando alguma IDE, eu adoro o Pycharm, basta instalar as bibliotecas do jeito que você sempre faz e escolher o ambiente que quer trabalhar.
Vamos ao que interessa!
Agora é só importar as libs dentro do programa
from requests_html import HTMLSession
from bs4 import BeautifulSoup
import csv
import json
Neste exemplo os dados serão extraídos da url de busca de aluguel de imóveis na cidade de Belém-PA
https: url_do_site/?pagina=1
É claro que você pode adaptar o código para extrair dados da cidade que você quiser.
Como vamos fazer uma requisição HTTP do tipo get nas páginas de onde vamos extrair os dados, precisamos de uma lib que execute essa tarefa, neste tutorial vamos usar a lib requests_html. Primeiro precisamos definir a variável que armazena a url e em seguida instanciar o objeto responsável por fazer a requisição:
url = " url_do_site/?pagina="next_page = 1session = HTMLSession()r1 = session.get(url + str(next_page))
Como usamos uma requisição tipo get, ela vai retornar uma resposta, que fica armazenada na variável r1. Você pode notar que o argumento do método get é a url mais uma string que define o número da página da qual estamos extraindo os dados, nesse caso a página 1 (armazenada na variável next_page). Fizemos isso pois depois vamos definir um laço de navegação para acessar todas as páginas de busca com imóveis disponíveis na cidade de Belém.
Agora que já temos todas as informações contidas no código fonte da página, precisamos de uma lib para manipular este código de maneira mais fácil.
BeautifulSoup
Utilizando a maravilhosa lib BeautifulSoup podemos facilmente navegar pelas tags e pelas classes do código HTML e raspar toda a informação lá contida. Inicialmente vamos armazenar todo o código fonte dá página na variável sp utilizando o comando abaixo:
sp = BeautifulSoup(r1.content, 'html5lib')
Extraindo o número de imóveis disponíveis na cidade de Belém
Precisamos extrair o número total de imóveis disponíveis para alugar em Belém. Na página 1 da busca encontramos a informação de que há 1380 imóveis disponíveis na cidade de Belém (é claro que quando você fizer a busca esse número pode ser diferente)
Inspecionando o código notamos que a informação está na seguinte classe (dentro de uma tag h1):
<h1 class = "js-summary-title heading-regular heading-regular__bold align-left results__title"
Vamos extrair esses dados utilizando o método find. O primeiro argumento do método indica a tag e o segundo argumento indica a classe. Como a informação está no formato texto vamos usar o comando .text.strip()[:-34] para eliminar a informação irrelevante e deixar apenas o número de imóveis, isto é, 1.380:
list = sp.find("h1", class_="js-summary-title heading-regular heading-regular__bold align-left results__title").text.strip()[:-34]
Portanto o número de imóveis (1.380) ficará armazenado na variável list. Contudo essa informação é uma string! Precisamos transformá-la em um número do tipo int. Para isso eu proponho a seguinte rotina:
for char in list:
if char in ".":
n = list.replace(char,'')
num = int(n)
Ou seja, primeiro transformamos a string 1.380 em 1380 e depois convertemos em um número do tipo int que fica armazenado na variável num.
Até aqui tudo muito simples, certo? Então vamos adiante…
Contando o número de páginas de pesquisa
Para poder extrair todas as informações sobre todos os 1380 imóveis precisamos saber quantas páginas de busca existem. Essa informação é fácil de obter: basta dividir o número total de imóveis pelo número de imóveis em cada página de busca (existem 24 imóveis por página). Se o resto dessa divisão for zero então obteremos um número inteiro que é igual ao número de páginas de busca. Por outro lado, se o resto da divisão for diferente de zero, basta somar uma unidade para obtermos o número de páginas. Tudo isso que eu escrevi nesse parágrafo pode ser sintetizado na seguinte rotina:
if num % 24 == 0:
pag = num/24
print(pag)
elif num % 24 != 0:
pag = int(num/24) + 1
print(pag)
Neste caso particular salvamos o número de páginas de busca na variável pag de modo que o comando print(pag) deve exibir que foram encontradas 58 páginas de busca neste exemplo.
Preparando o arquivo .csv
Nesse passo não há muita complicação, basta definir uma variável (nesse casso chamei de f), em seguida abrir o arquivo nomeando-o com a extensão .csv e então definí-lo como um arquivo de escrita. Por fim basta chamar o método writer
f = open("nome_do_arquivo.csv", "w", newline='')writer = csv.writer(f)
Feito isso você só vai precisar definir quais são as features que você quer extrair. Neste exemplo eu vou tentar extrair o máximo de features possível para criar um banco de dados o mais completo que eu puder. Conforme você pode ver abaixo, vamos extrair id, categoria, Estado, Aluguel, Condomínio, valor total, IPTU, CEP, etc.
writer.writerow(
["id", "Categoria", "Cidade", "Estado", "Aluguel", "Condominio", "Valor total", "IPTU", "Preço de venda", "CEP", "Bairro", "Rua", "Numero de fotos", "Numero", "Area", "Quartos", "Suites", "Estacionamento", "Banheiros", "Andar", "Tipo", "Data"])
Definindo o laço de navegação das páginas de busca
Uma vez que já definimos a variável pag que guarda o número de páginas de busca, podemos facilmente navegar entre todas elas escrevendo o seguinte código:
for i in range(int(pag)):
Agora vem a parte difícil
Inspecionando todo o código fonte verificamos que os dados pertinentes são gerados dinâmicamente e estão todos dentro de uma tag script. Para acessá-los vamos primeiro converter todo o código em texto
soup = BeautifulSoup(r1.text, 'html.parser')
Em seguida armazenamos todas as tags script numa lista chamada all_scripts
all_scripts = list_houses.findAll("script")
Fazendo uma análise mais profunda (você vai ter que olhar esse código gigante com calma pra poder enxergar. Então respira fundo e vai!) podemos ver que as informações realmente relevantes estão na posição 5 da lista all_scripts. Sendo assim basta colocar um [5] no final do último código
all_scripts = list_houses.findAll("script")[5]
Agora vem o pulo do gato
Se você printar na tela a lista all_scripts na posição 5 você verá que ela é quase um arquivo Json. Mas só quase!
Para converter essa informação em um arquivo Json de verdade você vai precisar eliminar o ruído. Eu fiz isso eliminando os 25 primeiros e os últimos 122 caracters da lista adicionando o comando .text.strip()[25:-122] no final do último código conforme você vê abaixo:
all_scripts = list_houses.findAll("script")[5].text.strip()[25:-122]
Isso garante que o arquivo de saída é um legítimo arquivo Json pronto pra ser analisado. O problema é que esse arquivo é gigantesco e portanto não dá pra entender nada se você não organizar o negócio direito. Para solucionar esse problema eu usei o site Json formater and validator que organiza o código para facilitar a leitura. Utilizando essa ferramenta fica muuuuito mais fácil analisar o Json
Na figura acima você pode ver como o conteúdo da lista all_scripts sem ruído fica organizado facilitando a extração dos dados. Uma análise mais cuidadosa nos mostra que toda a informação que precisamos está dentro do caminho “results”->“listings”.
Nós poderíamos ter usado a lib Selenium pra acessar as tags e as classes do código para extrair os dados, como é amplamente feito em muitos tutoriais de WebScraping. O problema é que a lib Selenium é uma ferramenta muito útil para automatizar processos, mas é ineficiente para extração de dados, uma vez que utiliza o navegador para simular a ação humana. Por outro lado utilizando um aquivo de texto no formato Json fica muito mais fácil e muito mais rápido fazer a raspagem dos dados.
Agora é só correr pro abraço, pae!
Basta carregar o conteúdo usando a lib Json e armazenar numa variável (que eu chamei de data)
data = json.loads(all_scripts)
Agora é só escrever tudo no arquivo.csv e no final pular pra próxima página (next_pag +=1) que o robô reiniciará o processo tudo de novo
for item in data['results']['listings']: writer.writerow([item['listing']['id'],
item['listing']['unitTypes'][0],
item['listing']['address']['city'],
estado,
aluguel,
cond,
total,
iptu,
venda,
cep,
bairro,
rua,
pics,
num,
area,
quartos,
suites,
Estacionamento,
banheiros,
andar,
tipo,
item['listing']['createdAt']])
next_page += 1
Este código poderia estar completo, mas falta um detalhe bastante importante
A limpeza dos dados
Como o nome sugere, o objetivo aqui é fazer um tratamento básico dos dados para garantir o mínimo de qualidade das informações. Um problema comum que eu encontrei nesse dataset é a ausência de alguns dados, o que faz o robô dar erro por não achar o campo que procura. Para resolver esse problema fiz uma pequena rotina de tratamento de erros pra garantir que o programa não vai parar quando encontrar um problema. Em síntese eu programei o robô para colocar NI (não informado) no arquivo .csv sempre que algum campo estiver ausente ou em branco. Além disso eliminei todos os cifrões dos valores dos imóveis para deixar os dados mais limpos.
Deixo a cargo do leitor a análise dessa parte do código pois não convém descrevê-la aqui.
for item in data['results']['listings']:
aluguel = item['listing']['pricingInfo']['price']
for char in aluguel:
if char in "R$":
aluguel = aluguel.replace(char, '')
iptu = item['listing']['pricingInfo']['yearlyIptu']
for char in iptu:
if char in "R$":
iptu = iptu.replace(char, '')
if iptu == "":
iptu = "NI"
try:
pics = len(item['listing']['images'])
except Exception as error:
pics = 0
rua = item['listing']['address']['street']
if rua == "":
rua = "NI"
estado = item['listing']['address']['stateAcronym']
if estado == "":
estado = "NI"
cep = item['listing']['address']['zipCode']
if cep == "":
cep = "NI"
bairro = item['listing']['address']['neighborhood']
if bairro == "":
bairro = "NI"
cond = item['listing']['pricingInfo']['monthlyCondoFee']
for char in cond:
if char in "R$":
cond = cond.replace(char, '')
total = item['listing']['pricingInfo']['rentalTotalPrice']
for char in total:
if char in "R$":
total = total.replace(char, '')
if cond == "":
cond = "NI"
total = aluguel#item['listing']['pricingInfo']['price']
venda = item['listing']['pricingInfo']['salePrice']
for char in venda:
if char in "R$":
venda = venda.replace(char, '')
if venda == "":
venda = "NI"
num = item['listing']['address']['streetNumber']
if num == "":
num = "NI"
try:
area = item['listing']['usableAreas'][0]
except Exception as error:
area = 0
try:
quartos = item['listing']['bedrooms'][0]
except Exception as error:
quartos = 0
try:
suites = item['listing']['suites'][0]
except Exception as error:
suites = 0
try:
Estacionamento = item['listing']['parkingSpaces'][0]
except Exception as error2:
Estacionamento = 0
try:
banheiros = item['listing']['bathrooms'][0]
except Exception as error2:
banheiros = 0
try:
andar = item['listing']['unitFloor']
except Exception as error2:
andar = 0
try:
tipo = item['listing']['usageTypes'][0]
except Exception as error2:
tipo = "NAO DEFINIDO" writer.writerow([item['listing']['id'],
item['listing']['unitTypes'][0],
item['listing']['address']['city'],
estado,
aluguel,
cond,
total,
iptu,
venda,
cep,
bairro,
rua,
pics,
num,
area,
quartos,
suites,
Estacionamento,
banheiros,
andar,
tipo,
item['listing']['createdAt']])
next_page += 1
Quando o robô terminar o trabalho você deve ter dentro da sua pasta uma planilha parecida com esta
Para baixar a planilha com todos os dados basta clicar no seguinte link:
legal, não?!
Considerações finais
Apresentei pra vocês nesse tuto um exemplo de como programar um robô WebScraper que coleta dados do site de aluguel de imóveis. Eu optei por extrair os dados da cidade de belém, mas nada impede você de adaptar o programa para extrair dados de qualquer cidade do país. No próximo artigo vou te ensinar como fazer a mesma coisa no site da Olx e como enviar todos os dados da planilha direto pro seu servidor local MySql usando o Pentaho.
A primeira vantagem de utilizar esse robô é que, como ele trabalha com arquivos de texto importados direto do site é muito difícil a detecção de ação de bot. A segunda vantagem é que ele é muito, muito, muito rápido. Você consegue extrair milhares e milhares de dados em uma fração de segundo.
Ah! se você quiser ver o código completo é só clicar aqui.
Muito obrigado por chegar até aqui! Qualquer dúvida é só deixar nos comentários ou me procurar no linkedin. Vou ficar extremamente feliz em conversar com você.
Até a próxima!