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_originale = 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_originale[columns_to_display] df = df.style.format(thousands='.') col0, col1, col2, col4 = st.columns(4, gap="large") with col1: vantaggioso_count = df_originale["Vantaggioso"].sum() total_rows = df_originale.shape[0] st.metric("Numero Immobili", int(total_rows), int(vantaggioso_count)) st.write('Locali') immobili_per_locali = df_originale.sort_values(by=["Locali"], ascending=[True]).groupby("Locali").size() st.bar_chart(immobili_per_locali, color = "#ffb7b7", height=120) with col2: vantaggioso_count = df_originale["Vantaggioso"].sum() total_rows = df_originale.shape[0] st.metric("Numero Immobili", int(total_rows), int(vantaggioso_count)) st.write('Prezzo') chart_data = df_originale["Prezzo"] st.line_chart(chart_data, color = "#FF4B4B", height=120) 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 and len(comune_input)>0: 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.")