Domanda Ciclare Script python per lo scraping

Psychonaut

Utente Jade
17 Giugno 2012
1,486
89
740
747
Ciao a tutti,

ho ripreso qualche giorno fa a programmare in python (conosco le basi), ho creato questo script per fare scraping sugli annunci di immobiliare.it .

Ma mi sono perso e non riesco a capire come ciclarlo per prendere tutti i dati presenti nel sito.

Python:
# SCRIPT PER RECUPERARE INFORMAZIONI SULLE CASE IN AFFITTO
import os
from os import name
from re import U
import requests
from bs4 import BeautifulSoup

city=input("Inserisci il nome della città: ") # INSERIRE NOME CITTÀ
city=city.lower() # CONVERTE LE LETTERE MAIUSCOLE IN MINUSCOLO
#print(city)
A=input("La cerchi una casa in provincia? si/no: ")
if A == "si":
    ubicazione=city + "-provincia"
    URL="https://www.immobiliare.it/affitto-case/" + ubicazione + "/?criterio=rilevanza"
    #print(URL)
#    print(ubicazione)
elif A == "no":
    ubicazione=city
    URL="https://www.immobiliare.it/affitto-case/" + ubicazione + "/?criterio=rilevanza"
    #print(URL)
#    print(ubicazione)
else:
    print("Inserisci una delle seguenti opzioni: si/no/entrambi")

    resp = requests.get(URL)
    #print(resp.status_code)
    soup = BeautifulSoup(resp.text, "lxml")
    via = soup.select('[class="in-card__title"]')
    prezzo = soup.select('[class="nd-list__item in-feat__item in-feat__item--main in-realEstateListCard__features--main"]')
    locali=soup.select('[class="in-feat__data"]')
    metri=soup.select('[class="nd-list__item in-feat__item"][aria-label="superficie"]')
    os.system("clear")
    print("Via:\t" ,via[0].text)
    print("Prezzo:\t",prezzo[0].text)
    print("Locali:\t",locali[0].text )
    print("Metri:\t",metri[0].text)
 
Un primo errore che vedo è che quell'else non dovrebbe esserci.
Usi if/elif per la provincia, quindi immagino che il paese di quegli url sia il medesimo. Secondo me devi togliere l'else in modo che quel codice venga eseguito da entrambi.
 
Allora i due tipi di URL che crea sono questi :

Nel caso cercassi casa a palermo centro l'url diventa questo :

"https://www.immobiliare.it/affitto-case/palermo/?criterio=rilevanza"

Invece l'URL per gli annunci in provincia è questo :

"https://www.immobiliare.it/affitto-case/palermo-provincia/?criterio=rilevanza"

La città dipende da quella che si inserisc in input, se inserisci milano ritorna la pagina con gli annunci di milano.

Ho aggiunto l'else finale per gestire eventuali errori in input :

Schermata del 2023-06-05 12-21-33.png
 
Potresti fare così, però otterrà i risultati della sola prima pagina:

Python:
# SCRIPT PER RECUPERARE INFORMAZIONI SULLE CASE IN AFFITTO
import os
import sys
from os import name
from re import U
import requests
from bs4 import BeautifulSoup

city=input("Inserisci il nome della città: ") # INSERIRE NOME CITTÀ
city=city.lower() # CONVERTE LE LETTERE MAIUSCOLE IN MINUSCOLO
URL="https://www.immobiliare.it/affitto-case/" + city
A=input("La cerchi una casa in provincia? si/no: ")
if A == "si":
    URL=URL + "-provincia"
elif A != "no":
    sys.exit("Errore: Inserisci una delle seguenti opzioni: si/no")

URL=URL + "/?criterio=rilevanza"
resp = requests.get(URL)
#print(resp.status_code)
soup = BeautifulSoup(resp.text, "lxml")
via = soup.select('[class="in-card__title"]')
prezzo = soup.select('[class="nd-list__item in-feat__item in-feat__item--main in-realEstateListCard__features--main"]')
locali=soup.select('[class="in-feat__data"]')
metri=soup.select('[class="nd-list__item in-feat__item"][aria-label="superficie"]')
os.system("clear")

for i in range(len(via)):
    print("Via:\t" ,via[i].text)
    print("Prezzo:\t",prezzo[i].text)
    print("Locali:\t",locali[i].text)
    print("Metri:\t",metri[i].text)
 
  • Love
Reazioni: Psychonaut
Ciao, grazie mille.

Ho un paio di domande, sys.exit gestisce gli errori in output?
Nel ciclo for con len ottengo il numero di elementi nella variabile "via" che poi passa a "range" per ciclare?

Per le altre pagine stavo pensando di fare qualcosa con selenium.
 
Ultima modifica:
Ciao, grazie mille.

Ho un paio di domande, sys.exit gestisce gli errori in output?
Nel ciclo for con len ottengo il numero di elementi nella variabile "via" che poi passa a "range" per ciclare?

Per le altre pagine stavo pensando di fare qualcosa con selenium.

sys.exit termina lo script con un messaggio di errore, serve a non proseguire col codice, se non esci nel mio codice darebbe per scontato aver scelto "no".

Non sviluppo quasi mai in python, ho fatto una rapida ricerca su google e ho letto che:
C:
for (int i = 0; i < 5; i++)
Diventa:
Python:
for i in range(5):

Sì, len ritorna il numero di elementi che combaciano col selettore usato per "via", considera però che con questo approccio se a qualche elemento manca un altro campo (la via c'è sicuramente in quanto titolo) avresti uno sfasamento, per fare un esempio se un risultato non ha specificato il numero dei locali gli verrebbe assegnato quello del risultato dopo (e relativo errore di out of bounds per l'ultimo risultato). Per risolvere potresti selezionare in base a le classi nd-list__item in-realEstateResults__item, assegnate ai li che contengono ogni risultato, e poi estrarre i dati solo dai suoi sotto-elementi così sei sicuro che sono correlati allo stesso immobile e puoi gestire casi dove alcune info mancano.

PS: alla fine volevo provare e l'ho scritto io con pagine multiple :asd:

Python:
# SCRIPT PER RECUPERARE INFORMAZIONI SULLE CASE IN AFFITTO
import os
import sys
import requests
from os import name
from re import U
from bs4 import BeautifulSoup

city=input("Inserisci il nome della città: ") # INSERIRE NOME CITTÀ
city=city.lower() # CONVERTE LE LETTERE MAIUSCOLE IN MINUSCOLO
URL="https://www.immobiliare.it/affitto-case/" + city
A=input("La cerchi una casa in provincia? si/no: ")
if A == "si":
    URL=URL + "-provincia"
elif A != "no":
    sys.exit("Errore: Inserisci una delle seguenti opzioni: si/no")

URL=URL + "/?criterio=rilevanza"
C=int(input("Quante pagine vuoi consultare? "))
for pag in range(C):
    request_url = URL
    if pag > 1:
        request_url=URL + "&pag=" + str(pag)
   
    resp = requests.get(request_url)
    soup = BeautifulSoup(resp.text, "lxml")
    results = soup.select('[class="nd-list__item in-realEstateResults__item"]')
    for i in range(len(results)):
        result = results[i]
        via = result.findChildren('a', {'class': 'in-card__title'})
        if len(via) == 0:
            continue
       
        print("Via:\t", via[0].text)
        prezzo = result.findChildren('li', {'class': 'nd-list__item in-feat__item in-feat__item--main in-realEstateListCard__features--main'})
        if len(prezzo) == 1:
            print("Prezzo:\t", prezzo[0].text)
       
        attrib = result.findChildren('div', {'class': 'in-feat__data'})
        for attr in attrib:
            p = attr.findParent()
            label = p["aria-label"]
            if label == "locali":
                print("Locali:\t", attr.text)
            elif label == "superficie":
                print("Metri:\t", attr.text)
       
        print("-----------------------\n")
 
  • Mi piace
Reazioni: Psychonaut
Ultima modifica:
Rispondo solo ora perchè ho cercato di capire un pò il codice che hai riportato.

Aaaalloora

Python:
request_url=URL + "&pag=" + str(pag)
In pratica leggi piú pagine aggiungendo semplicemente la stringa ?pag= che nell'url gestisce il numero della pagina che si sta visualizzando,e trasformi la variabile pag da intero a stringa :

1686180077428.png


Python:
resp = requests.get(request_url)
soup = BeautifulSoup(resp.text, "lxml")
results = soup.select('[class="nd-list__item in-realEstateResults__item"]')

poi con resp fai una get della pagina, crei l'oggetto soup che gira l'output della get al parser lxml e con la variabile results usi il metodo .select per selezionare la classe che contiene gli elementi che voglio selezionare :

1686181569166.png


Python:
    for i in range(len(results)):
        result = results[i]
        via = result.findChildren('a', {'class': 'in-card__title'})
        if len(via) == 0:
            continue
 
        print("Via:\t", via[0].text)

poi la fai leggere al ciclo for, una prima cosa che non ho capito è l'inizializzazione della variabile " result = results", poi inizializzi "via" con il metodo findChildren che estrae degli oggetti tag, in questo caso il tag 'a' che ha come attributo la classe in-card__title'.

un'altra cosa che non ho capito è lo statement if di via, forse per verificare che parta dal primo elemento di via?

La variabile prezzo utilizza lo stesso metodo della variabile via, e quindi mi è chiaro, ma questa volta nell'if statement controlli che sia uguale a 1, questo non mi è chiaro.

Python:
attrib = result.findChildren('div', {'class': 'in-feat__data'})
La variabile attrib invece possiede l'attributo che contiene sia i locali che i metri, che si differenziano solo per la label, e questo lo controlli con l'ultimo if/elif.

Un'altra cosa che non mi è chiara è lo [0] quando stampi le variabili, tipo :
Python:
        print("Via:\t", via[0].text)

Scusa se mi sono dilungato e ho fatto un pò di domande, ma mi interessa capire ciò che non so :)
 
Per prima cosa partiamo dai miei errori dovuti alla poca familiarità con python:
- La pagina 1 viene presa due volte, il loop doveva essere for pag in range(1, C + 1):
- Come hai giustamente notato, il seguente codice non è l'ideale:
Python:
for i in range(len(results)):
    result = results[i]
sarebbe meglio for result in results:

un'altra cosa che non ho capito è lo statement if di via, forse per verificare che parta dal primo elemento di via?
La variabile prezzo utilizza lo stesso metodo della variabile via, e quindi mi è chiaro, ma questa volta nell'if statement controlli che sia uguale a 1, questo non mi è chiaro.
In sostanza sono controlli extra per assicurarsi che il result che si sta parsando sia una in-realEstateResults__item come ce l'aspettiamo cioè che contiene almeno la via (immagina che aggiungono una card con la stessa classe per un altro scopo, col mio codice verrebbe skippata senza rompere tutto), la stessa cosa si può dire del prezzo dove me ne aspetto uno e uno solo per ogni risultato. Questi check aggiungono robustezza a cambiamenti piccoli del sito ed evitano crash ed errori in alcune condizioni impreviste.

Un'altra cosa che non mi è chiara è lo [0] quando stampi le variabili, tipo :
Python:
        print("Via:\t", via[0].text)

Perché findChildren, come select, ritorna un array di risultati: per quanto ne sa beautifulsoup potrebbero esserci N elementi di quella classe, prima di accedere con [0] al primo elemento controllo che ne esista almeno uno (con len). Come usavi nel tuo primo codice select aveva scope globale quindi via = soup.select('[class="in-card__title"]') ti ritornava tutte le vie della pagina, mentre nel mio con findChildren la via viene ricercata solo tra i sottoelementi di un risultato, per cui solo una via dovrebbe essere presente.