Когда ученый-исследователь данных ищет место для жизни
Table Of Content
Жил-был парень, который уехал из Северо-Востока Бразилии (Форталеза, штат Сеара) и отправился на поиски приключений в Сан-Паулу. У этого парня была серьезная проблема с принятием решений, и он, как и любой новичок, не знал, насколько дорого стоит снимать жилье в новом городе. Однако ему нужно было снять место для проживания в новом городе (Кампинас, штат Сан-Паулу), чтобы начать новое путешествие. Он начал с посещения всех сайтов, которые нашел в интернете, чтобы сравнить цены. После того, как он потерялся на нескольких сайтах, где объявления о недвижимости были представлены в разных форматах, он решил изменить подход. Будучи ученым-исследователем данных, он решил использовать свои знания для принятия решения. Вкратце, этот парень был я.
Я решил немного изучить парсинг и, очевидно, нашел много материалов, использующих нашего любимого Python. С помощью этой статьи https://medium.com/@henriquecoura_87435/webscraping-com-python-extraindo-dados-de-um-ecommerce-89c16b622f69 я смог написать довольно простой скрипт для сбора информации о квартирах с двумя спальнями и одним местом на парковке в Кампинасе, штат Сан-Паулу. Поскольку это был мой первый проект по парсингу, я считаю, что его можно существенно улучшить. Поэтому не стесняйтесь улучшать его в своих проектах. Я выбрал сайт OLX для получения данных, но это может быть применено к любому другому сайту.
Оригинальный код
Прямо к делу, ниже приведены использованные библиотеки. Полужирным шрифтом выделены библиотеки, которые я никогда не использовал, но в них нет ничего сложного.
**import lxml.html as parser**
import requests
import csv
import re
**from urllib.parse import urlsplit, urljoin**
import time
import numpy as np
import pandas as pd
import random
import matplotlib.pyplot as plt
Следующие функции я создал, чтобы сделать код более чистым. Они извлекают числа и текст из грязных строк вроде '\n\n\n\n\n\n\Cambui\t\t\t\t\t\t' или 'R$ 1.200 \n\n'.
def get_float(string_):
return float(''.join(re.findall('\d+',string_)))
def get_cep(string_):
return ''.join(re.findall('\d{5}-\d{3}',string_))
def sub_string(string_):
return string_.strip()
Следующая функция была немного сложнее и потребует некоторого времени, чтобы понять структуру HTML-страницы. Вам придется потратить время на просмотр, поиск и минимальное понимание структуры страницы. Не беспокойтесь, если вы ничего не понимаете, это процесс обучения и он стоит того.
def get_links_page(start_url):
r = requests.get(start_url)
html = parser.fromstring(r.text)
links = html.xpath("//a[@class='OLXad-list-link']/@href")
links = [urljoin(start_url, l) for l in links]
links = [urlsplit(l)._replace(query="").geturl() for l in links]
next_page = html.xpath("//div[@class='module_pagination']//ul[@class='list']//li[@class='item next']//a[@class='link'][@rel='next']/@href")[0]
return links, next_page
Эта функция получает посещенный URL при выполнении базового поиска квартиры с двумя спальнями в Кампинасе (https://sp.olx.com.br/grande-campinas/regiao-de-campinas/campinas/imoveis/aluguel/2-quartos?gsp=1). Функция извлекает все ссылки на объявления на этой странице и ищет на странице небольшие числа с пагинацией сайта. Этот адрес пагинации используется для получения следующей страницы с продолжением результатов поиска. Таким образом, можно переходить на вторую, третью, четвертую страницу и так далее. Последняя функция похожа на предыдущую, но более сложная. Это связано с тем, что она посещает все полученные ссылки и ищет несколько полей, описывающих недвижимость, и добавляет все это в замечательный dataframe (потому что так веселье будет полным).
def get_atributes(links):
tp_imovel = []
condominio = []
iptu = []
area = []
quartos = []
banheiros = []
garagem = []
prices = []
cep = []
bairro = []
city = []
url = links
for i in links:
time.sleep(random.randint(1,3))
r = requests.get(i)
product_html = parser.fromstring(r.text)
des_dict = dict(zip(
product_html.xpath("//ul[@class='list square-gray']/li[@class='item']/p[@class='text']/span[@class='term']/text()"),
product_html.xpath("//ul[@class='list square-gray']/li[@class='item']/p[@class='text']/strong[@class='description']/text()")))try:
tp_imovel.append(sub_string(des_dict['Tipo:']))
except:
tp_imovel.append('')
try:
condominio.append(get_float(des_dict['Condomínio:']))
except:
condominio.append(None)
try:
iptu.append(get_float(des_dict['IPTU:']))
except:
iptu.append(None)
try:
area.append(get_float(des_dict['Área útil:']))
except:
area.append(None)
try:
quartos.append(get_float(des_dict['Número de quartos:']))
except:
quartos.append(None)
try:
banheiros.append(get_float(des_dict['Número de banheiros:']))
except:
banheiros.append(None)
try:
garagem.append(get_float(des_dict['Vagas na garagem:']))
except:
garagem.append(None)
try:
city.append(sub_string(des_dict['Município:']))
except:
city.append('')
try:
cep.append(get_cep(des_dict['CEP do imóvel:']))
except:
cep.append('')
try:
bairro.append(sub_string(des_dict['Bairro:']))
except:
bairro.append('')
try:
prices.append(get_float(product_html.xpath("//h3[@class='price']/span[@class='actual-price']/text()")[0]))
except:
prices.append(None)
df = pd.DataFrame({'tp_imovel':tp_imovel,'price':prices,'cond':condominio,
'iptu':iptu,'area':area,'quartos':quartos,'banheiros':banheiros,
'garagem':garagem,'cep':cep,'City':city,'Bairro':bairro,'url':links})
return df
Теперь просто создайте цикл с созданными функциями.
df_t = pd.DataFrame()
next_page = '[https://sp.olx.com.br/grande-campinas/regiao-de-campinas/campinas/imoveis/aluguel/2-quartos?gsp=1'](https://sp.olx.com.br/grande-campinas/regiao-de-campinas/campinas/imoveis/aluguel/2-quartos?gsp=1%27)
for i in range(200):
print('Страница =>',i)
try:
n_links, next_page = get_links_page(next_page)
df = get_atributes(n_links)
df_t = pd.concat([df_t,df])
except:
print('Ошибка при поиске новых страниц.')
break
Первичный анализ данных после небольшой очистки, удаляющей некоторые выбросы с помощью Kmeans.
Вот и все, ребята. Если у вас есть вопросы, вот ссылка (https://github.com/samuelmorais1891/scraping_olx.git) на мой jupyter-notebook, не стесняйтесь отправлять вопросы или предложения. Спасибо.