Spaces:
Sleeping
Sleeping
import requests | |
from bs4 import BeautifulSoup | |
import json | |
import re | |
import pandas as pd | |
from io import BytesIO | |
from collections import namedtuple | |
import numpy as np | |
import streamlit as st | |
def clean_text(text): | |
return re.sub(r'\s+', ' ', text).strip() | |
def formatta_numero(stringa): | |
stringa = stringa.split(',')[0] | |
stringa = stringa.replace(".", "").replace("da", "").replace("€", "").replace("%","").replace("m²", "").replace("locali", "").strip() | |
stringa = stringa.split(' ')[0] | |
return stringa | |
# Estrae le informazioni dagli ANNUNCI | |
def extract_info(provincia, comune, prezzo_medio_mq, listing): | |
info = {} | |
superficie = "" | |
locali = "" | |
info['Provincia'] = provincia | |
info['Comune'] = comune | |
price_elem = listing.find('div', class_='in-listingCardPrice') | |
prezzo = clean_text(price_elem.text) if price_elem else "" | |
link_elem = listing.find('a', class_='in-listingCardTitle') | |
if link_elem: | |
link = link_elem['href'] | |
titolo = link_elem.text.strip() | |
feature_list = listing.find('div', class_='in-listingCardFeatureList') | |
if feature_list: | |
for item in feature_list.find_all('div', class_='in-listingCardFeatureList__item'): | |
use_elem = item.find('use', class_='nd-icon__use') | |
if use_elem: | |
if use_elem.get('xlink:href') == '#planimetry': | |
locali = item.find('span').text.strip() | |
elif use_elem.get('xlink:href') == '#size': | |
superficie = item.find('span').text.strip() | |
image_url = "" | |
img = listing.find('figure', class_='nd-figure nd-ratio in-photo') | |
if img: | |
image_url = img.find('img')['src'] | |
superficie = formatta_numero(superficie) | |
locali = formatta_numero(locali) | |
prezzo = formatta_numero(prezzo) | |
info['Immagine'] = image_url | |
info['Titolo'] = titolo | |
info['Prezzo'] = int(prezzo) | |
info['Superficie'] = int(superficie) | |
try: | |
prezzo_numerico = int(prezzo) | |
superficie_numerica = int(superficie) | |
info['PrezzoMq'] = prezzo_numerico // superficie_numerica | |
prezzo_medio_mq = formatta_numero(prezzo_medio_mq) | |
prezzo_medio_mq_numerico = int(prezzo_medio_mq) | |
differenza = prezzo_medio_mq_numerico - info['PrezzoMq'] | |
vantaggio = (differenza / prezzo_medio_mq_numerico) * 100 | |
vantaggio = max(0, vantaggio) | |
vantaggio = int(vantaggio) | |
except (ValueError, ZeroDivisionError): | |
info['PrezzoMq'] = 0 | |
vantaggio = 0 | |
info['Locali'] = int(locali) | |
info['Link'] = link | |
info['PrezzoMedioMq'] = int(prezzo_medio_mq) | |
info['Vantaggio'] = vantaggio | |
if info['PrezzoMq']< int(prezzo_medio_mq) and info['PrezzoMq']>0: | |
info['Vantaggioso'] = True | |
else: | |
info['Vantaggioso'] = False | |
return info | |
# Legge gli ANNUNCI (pagina x pagina) sulla base del COMUNE di appartenenza | |
def scrape_immobiliare(provincia, comune, prezzo_medio_mq, prezzo_minimo, prezzo_massimo, locali_minimo, locali_massimo): | |
print(provincia + " " + comune + " " + prezzo_medio_mq) | |
comune_url = comune.replace(" ", "-") | |
base_url = f"https://www.immobiliare.it/vendita-case/{comune_url}/?prezzoMinimo={prezzo_minimo}&prezzoMassimo={prezzo_massimo}&localiMinimo={locali_minimo}&localiMassimo={locali_massimo}&random=123456" | |
results = [] | |
page = 1 | |
url = base_url | |
while True: | |
print(f'Elaborazione pagina {page}') | |
response = requests.get(url) | |
soup = BeautifulSoup(response.content, 'html.parser') | |
listings = soup.find_all('li', class_='nd-list__item in-searchLayoutListItem') | |
if not listings: | |
break | |
for listing in listings: | |
results.append(extract_info(provincia, comune, prezzo_medio_mq, listing)) | |
pagination = soup.find('div', class_='in-pagination__list') | |
if pagination: | |
next_page = pagination.find('a', class_='in-pagination__item', string=lambda text: text and text.strip().isdigit()) | |
if not next_page: | |
break | |
page += 1 | |
url = base_url + '&pag=' + str(page) | |
return json.dumps(results, ensure_ascii=False, indent=2) | |
# Restituisce l'elenco dei COMUNI di una Provincia e il PREZZO MEDIO | |
def get_elenco_comuni(provincia): | |
base_url = f"https://www.immobiliare.it/mercato-immobiliare/lombardia/{provincia}-provincia/" | |
results = [] | |
print(f'Lettura Comuni e Prezzo Medio al Mq') | |
response = requests.get(base_url) | |
soup = BeautifulSoup(response.content, 'html.parser') | |
rows = soup.find_all('tr', class_='nd-table__row') | |
results = [] | |
for row in rows: | |
cells = row.find_all('td', class_='nd-table__cell') | |
if len(cells) >= 2: | |
comune = cells[0].get_text(strip=True) | |
prezzo_vendita = cells[1].get_text(strip=True) | |
results.append({ | |
'provincia': provincia, | |
'comune': comune, | |
'prezzo': prezzo_vendita | |
}) | |
return results | |
st.set_page_config(layout="wide") | |
st.title('🏠 Immobiliare A.I. ') | |
st.write("##### Il tuo assistente di intelligenza artificiale per la ricerca di occasioni immobiliari") | |
with st.expander("Informazioni"): | |
st.write("Immobiliare A.I. è la webapp che semplifica la ricerca di immobili, grazie a algoritmi avanzati che calcolano il vantaggio di ogni offerta. Trova le migliori occasioni sul mercato con analisi precise e personalizzate. Scopri l’immobile giusto per te con facilità e sicurezza!") | |
cerca_premuto = False | |
comuni_provincia = {} | |
with st.sidebar: | |
comuni_provincia = get_elenco_comuni('Brescia') | |
st.title("Filtri") | |
elenco = [d['comune'] for d in comuni_provincia] | |
comune_input = st.multiselect( | |
"Comuni", | |
elenco | |
) | |
prezzo_minimo = st.sidebar.slider("Prezzo Minimo", min_value=0, max_value=1000, value=200) | |
prezzo_massimo = st.sidebar.slider("Prezzo Massimo", min_value=0, max_value=1000, value=230) | |
locali = list(range(1, 21)) # Intervallo da 1 a 10 | |
# Select slider unico per selezionare l'intervallo del numero di locali | |
locali_range = st.sidebar.select_slider( | |
"Locali", | |
options=locali, | |
value=(locali[2], locali[4]) # Valore iniziale, da 1 a 5 locali | |
) | |
# Dividi il range in minimo e massimo numero di locali | |
locali_minimo, locali_massimo = locali_range | |
prezzo_minimo = prezzo_minimo*1000 | |
prezzo_massimo = prezzo_massimo*1000 | |
cerca_premuto = st.button("Cerca", use_container_width=True, type='primary') | |
# | |
#if __name__ == "__main__": | |
# print(df) | |
def scrivi_dataframe(output): | |
if len(output) > 0: | |
df = pd.DataFrame(output) | |
df = df.sort_values(by=["Vantaggio", "PrezzoMq"], ascending=[False, True]) | |
columns_to_display = ["Vantaggioso", "Vantaggio", "Immagine", "Comune", "Titolo", "PrezzoMq", "Prezzo", "Superficie", "Locali", "PrezzoMedioMq", "Link"] | |
df = df[columns_to_display] | |
df = df.style.format(thousands='.') | |
st.dataframe(df, hide_index=True, use_container_width=True, | |
column_config ={ | |
"Vantaggioso": st.column_config.CheckboxColumn("Vantaggioso"), | |
"Vantaggio": st.column_config.ProgressColumn( | |
"Punteggio", | |
help="Vantaggio in %", | |
format='%f', | |
min_value=0, | |
max_value=100, | |
), | |
"Immagine": st.column_config.ImageColumn("Anteprima", help="Anteprima", width="small"), | |
"PrezzoMq": "€/Mq", | |
"PrezzoMedioMq": "Media €/Mq", | |
"Prezzo": st.column_config.NumberColumn( | |
"Prezzo Totale", | |
help="Il prezzo totale dell'immobile in EURO", | |
step=1, | |
format="%d €", | |
), | |
"Superficie": "Superficie", | |
"Locali": "Locali", | |
"Link": st.column_config.LinkColumn("App URL") | |
}) | |
if cerca_premuto: | |
comuni_selezionati = comune_input | |
comuni_selezionati = [comune.upper() for comune in comuni_selezionati] | |
output = [] | |
output_singolo = [] | |
for comune_provincia in comuni_provincia: | |
if comune_provincia['comune'].upper() in comuni_selezionati: | |
with st.spinner(f"Ricerca Immobili Comune: {comune_provincia['comune']}"): | |
output_singolo = json.loads(scrape_immobiliare(comune_provincia['provincia'], | |
comune_provincia['comune'], | |
comune_provincia['prezzo'], | |
prezzo_minimo, | |
prezzo_massimo, | |
locali_minimo, | |
locali_massimo)) | |
st.write(f"### {comune_provincia['comune']}") | |
scrivi_dataframe(output_singolo) | |
st.divider() | |
output += output_singolo | |
if len(comuni_selezionati)>1: | |
st.write(f"### Comuni Selezionati") | |
scrivi_dataframe(output) | |
st.success("Elaborazione Completata") | |
else: | |
st.error("Per favore, inserisci il nome di un comune.") |