Аренда жилья - способ Data Science Часть 1: парсинг всего с помощью Python и BeautifulSoup - ОБНОВЛЕНО
Table Of Content
- Построение собственного набора данных
- Приготовим суп - ОБНОВЛЕНО
- Получаем районы внутри города (районы)
- Теперь нам нужно найти все ссылки на объявления, чтобы по одному спарсить информацию внутри них
- Проверяем, имеет ли это смысл, и сохраняем
- Повар на работе!
- Теперь мы передаем все ссылки на объявления в функцию scrape_link, чтобы получить информацию о квартирахdf_scrape = pd.DataFrame()
- Срезка, обрезка и очистка
- Приготовьте горшок!
В прошлом году я переехал из своего первоначального дома в новый город и сменил работу. Все произошло так быстро, и у меня было всего несколько недель, чтобы найти жилье перед началом новой работы. В этой суете у меня не было достаточно времени, чтобы разобраться в рынке недвижимости в городе, и в итоге я выбрал жилье, которое лучше всего сочеталось между расстоянием от работы и сервисами. Но... ну... жилье довольно маленькое, и я подозревал, что плачу слишком много за этот дом. Но я только догадывался!
Итак... что, если я смогу это выяснить? Итак... вернемся к тому, что я изучал в области машинного обучения.
Построение собственного набора данных
Это довольно распространенный и стандартный пример применения машинного обучения: регрессия на цены домов для определения их реальной стоимости. Вы можете найти примеры этого где угодно в Интернете. За исключением того, что обычно они используют наборы данных, полученные откуда-то... ну... откуда-то. Мне нужны свежие цены, полученные из города, который я хочу, и я хочу, чтобы они обновлялись в течение нескольких месяцев. Есть только один способ сделать это: парсинг!
Я живу в Италии, точнее в Турине. В Италии самый большой веб-сайт, где собраны все объявления о аренде или покупке домов, это www.immobiliare.it
Immobiliare.it собирает объявления, которые каждое агентство в Италии может использовать, чтобы показать объекты недвижимости, с которыми они работают, поэтому это, вероятно, лучший способ получить представление о рынке недвижимости в конкретном городе. И теперь пришло время приступить к работе.
Приготовим суп - ОБНОВЛЕНО
ОБНОВЛЕНИЕ: разработчики immobiliare.it обновили веб-сайт с новым дизайном и немного сложнее разметкой HTML для парсинга. Я обновлю представленный код, чтобы отразить изменения.
Что мы собираемся сделать, это зайти на веб-сайт, перейти на главную страницу нашего города, собрать список всех районов города и спарсить каждое объявление, опубликованное в этом районе. Набор инструментов:
import requests
from bs4 import BeautifulSoup
import pandas as pd
from tqdm import tqdm_notebook as tqdm
import csv
Теперь мы готовы начать!
def get_pages(main):
try:
soup = connect(main)
n_pages = [_.get_text(strip=True) for _ in soup.find('ul', {'class': 'pagination pagination__number'}).find_all('li')]
last_page = int(n_pages[-1])
pages = [main]
for n in range(2,last_page+1):
page_num = "/?pag={}".format(n)
pages.append(main + page_num)
except:
pages = [main]
return pages
def connect(web_addr):
resp = requests.get(web_addr)
return BeautifulSoup(resp.content, "html.parser")
def get_areas(website):
data = connect(website)
areas = []
for ultag in data.find_all('ul', {'class': 'breadcrumb-list breadcrumb-list_list breadcrumb-list__related'}):
for litag in ultag.find_all('li'):
for i in range(len(litag.text.split(','))):
areas.append(litag.text.split(',')[i])
areas = [x.strip() for x in areas]
urls = []
for area in areas:
url = website + '/' + area.replace(' ','-').lower()
urls.append(url)
return areas, urls
def get_apartment_links(website):
data = connect(website)
links = []
for link in data.find_all('ul', {'class': 'annunci-list'}):
for litag in link.find_all('li'):
try:
links.append(litag.a.get('href'))
except:
continue
return links
def scrape_link(website):
data = connect(website)
info = data.find_all('dl', {'class': 'im-features__list'})
comp_info = pd.DataFrame()
cleaned_id_text = []
cleaned_id__attrb_text = []
for n in range(len(info)):
for i in info[n].find_all('dt'):
cleaned_id_text.append(i.text)
for i in info[n].find_all('dd'):
cleaned_id__attrb_text.append(i.text)
comp_info['Id'] = cleaned_id_text
comp_info['Attribute'] = cleaned_id__attrb_text
comp_info
feature = []
for item in comp_info['Attribute']:
try:
feature.append(clear_df(item))
except:
feature.append(ultra_clear_df(item))
comp_info['Attribute'] = feature
return comp_info['Id'].values, comp_info['Attribute'].values
def remove_duplicates(x):
return list(dict.fromkeys(x))
def clear_df(the_list):
the_list = (the_list.split('\n')[1].split(' '))
the_list = [value for value in the_list if value != ''][0]
return the_list
def ultra_clear_df(the_list):
the_list = (the_list.split('\n\n')[1].split(' '))
the_list = [value for value in the_list if value != ''][0]
the_list = (the_list.split('\n')[0])
return the_list
Итак, мы только что определили 5 функций:
- connect(): используется для подключения к веб-сайту и загрузки исходного HTML-кода с него;
- get_areas(): парсит исходный HTML, чтобы найти районы. Для каждого района есть уникальная ссылка, которая фильтрует объявления, относящиеся только к этому району;
- get_pages(): для каждой "главной страницы" района он ищет, сколько страниц объявлений доступно, и создает ссылку для каждой отдельной страницы;
- get_apartment_links(): для каждой найденной страницы он ищет каждое объявление и собирает каждую ссылку;
- scrape_link(): эта функция представляет собой процесс фактического парсинга объявлений.
По окончании выполнения у нас будет ссылка на каждое отдельное объявление с указанием района происхождения.
# Получаем районы внутри города (районы)
website = "https://www.immobiliare.it/affitto-case/torino"
districts = get_areas(website)
print("Вот ссылки на районы **\n**")
print(districts)
# Теперь нам нужно найти все ссылки на объявления, чтобы по одному спарсить информацию внутри них
address = []
location = []
try:
for url in tqdm(districts):
pages = get_pages(url)
for page in pages:
add = get_apartment_links(page)
address.append(add)
for num in range(0,len(add)):
location.append(url.rsplit('/', 1)[-1])
except Exception as e:
print(e)
continue
announces_links = [item for value in address for item in value]
# Проверяем, имеет ли это смысл, и сохраняем
print("Количество объявлений:**\n**")
print(len(announces_links))
with open('announces_list.csv', 'w') as myfile:
wr = csv.writer(myfile)
wr.writerow(announces_links)
Теперь у нас есть ссылка на каждое объявление в этом конкретном городе, поэтому давайте искать сокровище.
Повар на работе!
## Теперь мы передаем все ссылки на объявления в функцию scrape_link, чтобы получить информацию о квартирахdf_scrape = pd.DataFrame()
to_be_dropped = []
counter = 0
for link in tqdm(list(announces_links)):
counter=counter+1
try:
names, values = scrape_link(link)
temp_df = pd.DataFrame(columns=names)
temp_df.loc[len(temp_df), :] = values[0:len(names)]
df_scrape = df_scrape.append(temp_df, sort=False)
except Exception as e:
print(e)
to_be_dropped.append(counter)
print(to_be_dropped)
continue## В конечном итоге сохраняем полезную информацию, полученную в процессе парсингаpd.DataFrame(location).to_csv('location.csv', sep=';')
pd.DataFrame(to_be_dropped).to_csv('to_be_dropped.csv', sep=';')
Этот код проходит через каждое объявление и извлекает из него информацию, собирая все данные в 2 списка: _nomi _и _valori. Первый содержит название характеристики, второй содержит значение. В конце процесса парсинга у нас наконец есть Pandas.DataFrame, в котором хранится каждое объявление со своими характеристиками и районом, к которому оно относится. Просто проверьте, имеет ли полученный DataFrame смысл.
print(df_scrape.shape)
df_scrape[‘district’] = location
df_scrape[‘links’] = announces_links
df_scrape.columns = map(str.lower, df_scrape.columns)
df_scrape.to_csv(‘dataset.csv’, sep=”;”)
Теперь у нас есть DataFrame, который содержит 24 столбца (24 характеристики), с помощью которых мы можем обучить наш алгоритм регрессии.
Теперь, прежде чем положить все в кастрюлю, мы должны очистить и нарезать ингредиенты...
Срезка, обрезка и очистка
К сожалению, набор данных не совсем... ну... готов. То, что мы собрали, часто является грязным и непригодным для работы. Приведу несколько примеров: цены хранятся в виде строк в формате "600 €/месяц", дома с более чем 5 комнатами указаны как "6+", и так далее.
Итак, у нас есть инструмент для "Очистки всех" ('Голлум, Голлум!')
df_scrape = df_scrape[['contratto', 'zona', 'tipologia', 'superficie', 'locali', 'piano', 'tipo proprietà', 'prezzo', 'spese condominio', 'spese aggiuntive', 'anno di costruzione', 'stato', 'riscaldamento', 'climatizzazione', 'posti auto', 'links']]def cleanup(df):
price = []
rooms = []
surface = []
bathrooms = []
floor = []
contract = []
tipo = []
condominio = []
heating = []
built_in = []
state = []
riscaldamento = []
cooling = []
energy_class = []
tipologia = []
pr_type = []
arredato = []
for tipo in df['tipologia']:
try:
tipologia.append(tipo)
except:
tipologia.append(None)
for superficie in df['superficie']:
try:
if "м" in superficie:
#z = superficie.split('|')[0]
s = superficie.replace(" м²", "")
surface.append(s)
except:
surface.append(None)
for locali in df['locali']:
try:
rooms.append(locali[0:1])
except:
rooms.append(None)
for prezzo in df['prezzo']:
try:
price.append(prezzo.replace("Аренда ", "").replace("€ ", "").replace("/месяц", "").replace(".",""))
except:
price.append(None)
for contratto in df['contratto']:
try:
contract.append(contratto.replace("\n ",""))
except:
contract.append(None)
for piano in df['piano']:
try:
floor.append(piano.split(' ')[0])
except:
floor.append(None)
for tipologia in df['tipo proprietà']:
try:
pr_type.append(tipologia.split(',')[0])
except:
pr_type.append(None)
for condo in df['spese condominio']:
try:
if "месяц" in condo:
condominio.append(condo.replace("€ ","").replace("/месяц",""))
else:
condominio.append(None)
except:
condominio.append(None)
for ii in df['spese aggiuntive']:
try:
if "год" in ii:
mese = int(int(ii.replace("€ ","").replace("/год","").replace(".",""))/12)
heating.append(mese)
else:
heating.append(None)
except:
heating.append(None)
for anno_costruzione in df['anno di costruzione']:
try:
built_in.append(anno_costruzione)
except:
built_in.append(None)
for stato in df['stato']:
try:
stat = stato.replace(" ","").lower()
state.append(stat)
except:
state.append(None)
for tipo_riscaldamento in df['riscaldamento']:
try:
if 'Централизованное' in tipo_riscaldamento:
riscaldamento.append('централизованное')
elif 'Автономное' in tipo_riscaldamento:
riscaldamento.append('автономное')
except:
riscaldamento.append(None)
for clima in df['climatizzazione']:
try:
cooling.append(clima.lower().split(',')[0])
except:
cooling.append('None')
final_df = pd.DataFrame(columns=['contract', 'district', 'renting_type', 'surface', 'locals', 'floor', 'property_type', 'price', 'spese condominio', 'other_expences', 'building_year', 'status', 'heating', 'air_conditioning', 'energy_certificate', 'parking_slots'])#, 'Arredato S/N'])
final_df['contract'] = contract
final_df['renting_type'] = tipologia
final_df['surface'] = surface
final_df['locals'] = rooms
final_df['floor'] = floor
final_df['property_type'] = pr_type
final_df['price'] = price
final_df['spese condominio'] = condominio
final_df['heating_expences'] = heating
final_df['building_year'] = built_in
final_df['status'] = state
final_df['heating_system'] = riscaldamento
final_df['air_conditioning'] = cooling
#final_df['classe energetica'] = energy_class
final_df['district'] = df['zona'].values
#inal_df['Arredato S/N'] = arredato
final_df['announce_link'] = announces_links
return final_dffinal = cleanup(df_scrape)
final.to_csv('regression_dataset.csv', sep=";")
Эта функция обрабатывает грязные данные разными способами, в зависимости от типа грязи. Большинство из них были очищены с помощью Regex (благословенны ими) и в целом с помощью инструментов для работы со строками. Взгляните на скрипт, чтобы увидеть, как он работает. PS: некоторые ошибки в наборе данных могут остаться. Обработайте их по своему усмотрению.
Приготовьте горшок!
Вот мы и здесь! Теперь у нас есть собственный набор данных, с которым мы можем работать с помощью машинного обучения и регрессии. В следующей статье я расскажу, как я справился со всеми ингредиентами.
Следите за обновлениями!
Ссылка на GitHub https://github.com/wonka929/house_scraping_and_regression
Эта статья является первой частью учебного пособия. Вторую часть статьи вы можете найти по этой ссылке: https://medium.com/@wonka929/house-rental-the-data-science-way-part-2-train-a-regression-model-tpot-and-auto-ml-9cdb5cb4b1b4
ОБНОВЛЕНИЕ: в процессе работы с новым веб-сайтом immobiliare.it я решил также обновить методологию регрессии. Вот новая, обновленная статья, которую вы можете найти онлайн:_https://wonka929.medium.com/house-rental-the-data-science-way-part-2-1-train-and-regression-model-using-pycaret-72d054e22a78