Python e Selenium per Web Scraping

Introduzione

A volte può essere necessario consultare più volte lo stesso sito alla ricerca di qualche informazione aggiornata. Ad esempio per verificare le previsioni meteo di una determinata città, oppure per controllare la disponibilità di un servizio o articolo che si desidera acquistare.

Oltre a recarsi manualmente e personalmente sul sito in questione, c’è la possibilità di scrivere uno script che si occupi più o meno automaticamente di reperire le informazioni richieste. Questa procedura è chiamata Web Scraping.

Per la creazione di questo script si può usare qualsiasi linguaggio di programmazione, ma spesso – per semplicità e comodità – si adopera Python un linguaggio interpretato.

Vediamo come fare!

Installazione

  1. Installiamo la versione 3.5 di Python, scaricando dal sito ufficiale la versione più recente. Questo tutorial usa la versione 3.5 (32 bit).
  2. Dopo l’installazione, è necessario installare la libreria Selenium che si occupa di Web Scraping & Automation su Python. La libreria si installa semplicemente aprendo il prompt dei comandi, digitando:
    pip install Selenium
    

    “pip” è un utility inclusa nell’installazione base di Python. Se digitando il comando di sopra, non si avvia l’installazione c’è un problema di impostazione del PATH di Python. Nulla di grave, per risolvere rapidamente, portatevi con il prompt dei comandi alla cartella SCRIPTS contenuta dentro la vostra installazione di Python (esempio: C:\Users\NOMEUTENTE\AppData\Local\Programs\Python\Python35-32\Scripts) e rilanciate il comando; troverete pip.exe che vi consentirà di lanciare la procedura di installazione di Selenium.

  3. Scarichiamo ChromeDriver, una versione “semplificata” di Chrome che verrà adoperata da Selenium per il Web Scraping.
  4. Creiamo una cartella dove andremo a posizionare il nostro script python e copiamo il file chromedriver.exe che abbiamo scaricato al punto 3.

Siamo pronti!

Lo Script

È il momento di scrivere il nostro script. Per questo esempio, andremo a reperire informazioni dal sito https://fibra.click/, comodissimo sito per verificare la copertura FTTC di Tim.

Prima di cominciare una parola di avvertimento: la procedura di Web Scraping, se portata all’eccesso (numerosissime richieste in un arco limitato di tempo) è chiaramente malvista poiché va ad appesantire spesso inutilmente il sito in questione (alla stregua di un attacco DDOS). Diversi siti cercano attivamente di scongiurare questa pratica, anche per questa ragione. Lo script che faremo con questo tutorial, comunque, utilizza una vera e propria pagina web per inviare le richieste di dati, procedura comunque lenta (si simula l’intevento di un utente) che non provoca alcun disagio al sito in questione, ma comunque ricordiamoci di non sovraccaricare i siti che andiamo ad analizzare.

Per prima cosa creiamo un nuovo file di testo, chiamiamolo WebScrapeTutorial.py e salviamolo nella cartella in cui abbiamo messoil file chromedriver.exe. Possiamo usare un qualsiasi editor di testo per scrivere il codice dello script, da blocco note, passando per Notepad++ fino a IDLE il piccolo editor incluso con Python. Scegliete lo strumento che volete.

Iniziamo quindi a digitare il codice:

#!/usr/bin/env python
# -*- coding: latin-1 -*-

import os
import time
import sys
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

 

Queste prime linee si occupano di impostare l’encoding di testo (per poter usare le lettere accentate) e di caricare le librerie che andremo ad utilizzare.


application_path = os.path.dirname(__file__)
chromedrv_path = application_path+"\chromedriver.exe"

Qui dichiariamo due variabili. La prima memorizza il path in cui è posizionato il file .Py, la seconda invece crea il percorso completo per il file chromedriver.exe necessario a Selenium per operare correttamente.


# FUNCTIONS
def GiveInfo():
    print("CENTRALE: \n" + central_planned + "\n") 

    # Format the cabinets data
    try:
        lines = cabinets_planned.splitlines()
        lines.remove('torna alla lista delle centrali')
        lines.remove('Clicca qui per scoprire')
        lines.remove('come identificare l\'armadio')
        lines.remove('torna alla lista delle centrali')
        for item in lines:
            if item:
                print(item)
    except:
        print("\nNon ci sono armadi pianificati. Spiacente!")

    print("\n" + lastUpdate + "\n")

Definiamo una semplice funzione chiamata GiveInfo() il cui compito è quello di mostrare a schermo le informazioni raccolte dal nostro script. Dopo un print() iniziale che formatta il testo da mostrare a schermo, si apre un blocco try…except.
Nel blocco try si prende la variabile cabinets_planned (che vedrete più avanti nel codice) e la si divide in diverse linee, andandola quindi a memorizzare nella variabile lines.
Quindi si cerca di rimuovere da questa variabile, le linee che non vogliamo mostrare a schermo perché inutili; sono linee che appaiono sul sito che andremo a “scrapare”, ma che non servono per l’output finale.
A questo punto, con un ciclo for, stampiamo a schermo le linee restanti: esse saranno le informazioni riguardo tutti gli armadi ripartilinea pianificati.
Il blocco except si occupa di intercettare un errore (cioè l’impossibilità di trovare le linee che cerchiamo di rimuovere) e di mostrare a schermo un messaggio che spiega che non ci sono armadi pianificati.


print ("Inserisci la città per la quale verificare la copertura FTTC: ")
city = input()

if not city:
    print("Città default: Melegnano\nIn lavorazione...\n")
    city = "Melegnano"
else:
    print("Città scelta: " + city + "\nIn lavorazione...\n")

Con questo codice inizia lo script vero e proprio. Con il comando print() mostriamo a schermo il testo nel quale chiediamo all’utente di inserire il comune per il quale vuole verificare la copertura (il campo COMUNE che bisogna inserire sul sito fibra.click), mentre con il comando input() raccogliamo l’input dell’utente e lo mettiamo nella variabile city.

A seguire facciamo un controllo mediante if…else per verificare se l’utente ha digitato o meno una città. Se non l’ha digitata, inseriamo una città default (così si evita di incorrere in un errore, ma è anche comodo nel caso si voglia controllare frequentemente lo stesso comune), in caso contrario (else) mostriamo a schermo la città scelta.


driver = webdriver.Chrome(chromedrv_path)
driver.set_window_position(-5000, 0)
driver.get('https://fibra.click/');

time.sleep(1) # Pausa di 1 secondo prima del prossimo comando

È il momento di utilizzare Selenium! Creiamo una variabile chiamata driver e quindi carichiamo un web driver Chrome passando come parametro il percorso in cui si trova il chromedriver.exe. Posizioniamo la schermata di Chrome al di fuori dello schermo (per non mostrarla all’utente, dato che lo script si occuperà di automatizzarne le interazioni, e poi con il comando GET andiamo a caricare il sito in questione.
In chiusura il comando time.sleep() permette di aspettare un secondo prima dei prossimi comandi, per dare il tempo a Selenium di aprire la pagina scelta. Questo valore potrebbe dover aumentare nei siti più complessi e pesanti.


search_box = driver.find_element_by_class_name('input_home')
search_box.send_keys(city)
search_box.send_keys(Keys.RETURN)

time.sleep(3) # Pausa di 3 secondi prima del prossimo comando

Adesso inizia la “magia”. Cerchiamo l’elemento “input_home” nel sito e lo memoriziamo nella variabile search_box.
Andando ad aprire il codice sorgente del sito fibra.click, possiamo infatti vedere che il campo dove inserire il nome del comune si chiama proprio INPUT_HOME. Così facendo possiamo dire a Selenium qual è il campo col quale dovrà interagire.

Ecco infatti una parte del codice HTML del sito in questione:


<div class="block1 centered">
			<h2>Con FibraClick puoi verificare la<br>
				copertura della fibra FTTC di TIM</h2>
			<br>
			<img class="home_artwork" alt="" src="/resources/home_artwork.png">
			<br>
			<p>Inserisci il nome del comune<br>per effettuare la ricerca.</p>
			<br>
			<div class="input_ricerca">
				<input type="text"
					   class="input_home"
					   placeholder="Esempio: Trento"
					   v-model="cap"
					   v-on:keypress.13="search">
				<a class="button" v-on:click="search"><img src="resources/search_icon.png" style="width:25px;margin-top:5px"></a>
			</div>
			<br><br>
		</div>

Si vede che la “class” della casella di testo si chiama proprio input_ricerca.
Il comando send_keys() serve proprio a inviare il testo alla casella di testo, nello specifico il nome della città chiesta all’utente all’inizio dello script.

Il successivo send_keys(Keys.RETURN) fa proprio quello che sembra: preme il pulsante INVIO, dando il via alla ricerca!


try:
    planned = driver.find_element_by_xpath('//*[@id="risultati_centrali"]/table/tbody/tr/td[2]')
    central_planned = planned.text  #get the planned data for central
    planned.click()     #click on the central to get the cabinets
    time.sleep(3)       #give some time to load info
except:
    print("Non ho trovato alcuna centrale pianificata per " + city)
    central_planned = "Non pianificata"

Qui inizia un blocco try…except nel quale andiamo a cercare il campo sul sito che mostra la data (se presente) di pianificazione della centrale per il comune ricercato.

In questo caso, invece di usare find_element_by_class, utiliziamo un altro sistema: by_xpath.
XPATH è il “nome” di un elemento specifico che magari non ha una sua classe o nome specificato. Trovare l’XPATH in questione è semplice: basta aprire il sito in questione con Chrome, cliccare col destro sul campo / dato per il quale si vuol conoscere l’XPATH (nel nostro caso la riga in arancione o verde con su scritto “Pianificato/Attivo per/il DATA”) e cliccare su ISPEZIONA. A questo punto apparirà una finestrella a lato, li basta cliccare con il pulsante destro del mouse sulla linea già evidenziata (che mostrerà il testo in questione) e fare click su Copy > Copy XPATH. Ora abbiamo l’XPATH dell’elemento che ci serve, e possiamo quindi inserirlo come argomento della funzione find_element_by_xpath().

Subito dopo, creiamo una variabile chiamata central_planned (che già abbiamo visto nella funzione GetInfo()) e vi mettiamo il testo del campo che abbiamo trovato con la funzione di cui sopra.
Il comando click() che segue serve a cliccare sopra l’elemento in questione, che nel sito fibra.click serve per allargare la centrale scelta per andare ad analizzare gli armadi pianificati.
Segue la classica attesa (in questo caso di tre secondi).

Il blocco except che viene chiamato in causa se non viene trovato l’elemento in questione (nel caso, ad esempio, di assenza totale di pianificazione per il comune scelto) si occupa di dare un messaggio d’errore all’utente, senza causare però alcun crash.


# get the cabinets

try:
    cabinets = driver.find_element_by_xpath('//*[@id="risultati_armadi"]')
    cabinets_planned = cabinets.text
except:
    print("Non ho trovato alcun armadio pianificato per " + city)
    cabinets_planned = "Non pianificati"

In questo blocco successivo, si fa esattamente la stessa cosa, andando però a cercare l’XPATH dell’elemento che mostra i risultati degli armadi pianificati. Come prima, si procede manualmente sul sito e si ricerca l’XPATH che ci serve.


#get date
try:
    updated = driver.find_element_by_xpath('//*[@id="block2"]/span')
    lastUpdate = updated.text
except:
    lastUpdate = "Non sono riuscito a trovare la data dell'ultimo aggiornamento."

L’ultimo blocco ha lo scopo di andare a reperire la nota in fondo alla pagina che mostra, nel nostro caso, a quale versione corrispondono i dati TIM di questa ricerca.


GiveInfo()

print ("Premi invio per chiudere!")
check = input()

driver.quit()

Ecco finalmente la chiamata alla funzione che abbiamo definito all’inizio. Ora abbiamo tutte le variabili colme di informazioni e la funzione GiveInfo() può mostrarle in maniera pulita e ordinata all’utente.

Segue quindi la richiesta di chiusura seguita dall’attuale chiusura del driver e dello script.

Ecco nella sua interezza il codice completo dello script:


#!/usr/bin/env python
# -*- coding: latin-1 -*-

import os
import time
import sys
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

application_path = os.path.dirname(__file__)
chromedrv_path = application_path+"\chromedriver.exe"

# FUNCTIONS
def GiveInfo():
    print("CENTRALE: \n" + central_planned + "\n") 

    # Format the cabinets data    
    try:
        lines = cabinets_planned.splitlines()
        lines.remove('torna alla lista delle centrali')
        lines.remove('Clicca qui per scoprire')
        lines.remove('come identificare l\'armadio')    
        lines.remove('torna alla lista delle centrali')  
        for item in lines:
            if item:
                print(item)
    except:
        print("\nNon ci sono armadi pianificati. Spiacente!")

    print("\n" + lastUpdate + "\n")

print ("Inserisci la città per la quale verificare la copertura FTTC: ")
city = input()

if not city:
    print("Città default: Melegnano\nIn lavorazione...\n")
    city = "Melegnano"
else:
    print("Città scelta: " + city + "\nIn lavorazione...\n")

driver = webdriver.Chrome(chromedrv_path)  
driver.set_window_position(-5000, 0)
driver.get('https://fibra.click/');

time.sleep(1) # Pausa di 1 secondo prima del prossimo comando

search_box = driver.find_element_by_class_name('input_home')
search_box.send_keys(city)
search_box.send_keys(Keys.RETURN)

time.sleep(3) # # Pausa di 3 secondi prima del prossimo comando

try:
    planned = driver.find_element_by_xpath('//*[@id="risultati_centrali"]/table/tbody/tr/td[2]')        
    central_planned = planned.text  #get the planned data for central
    planned.click()     #click on the central to get the cabinets
    time.sleep(3)       #give some time to load info
except:
    print("Non ho trovato alcuna centrale pianificata per " + city)
    central_planned = "Non pianificata"

# get the cabinets

try:
    cabinets = driver.find_element_by_xpath('//*[@id="risultati_armadi"]')
    cabinets_planned = cabinets.text
except:
    print("Non ho trovato alcun armadio pianificato per " + city)
    cabinets_planned = "Non pianificati"

#get date
try:
    updated = driver.find_element_by_xpath('//*[@id="block2"]/span')
    lastUpdate = updated.text
except:    
    lastUpdate = "Non sono riuscito a trovare la data dell'ultimo aggiornamento."

GiveInfo()

print ("Premi invio per chiudere!")
check = input()

driver.quit()

Spero che questo breve tutorial vi sia stato utile! Happy scraping! 🙂

Pubblicato il 13 luglio 2016, in Informatica, Programmazione con tag , , , , . Aggiungi il permalink ai segnalibri. 2 commenti.

  1. molto interessante, complimenti!!

    "Mi piace"

Lascia un commento