Web scraping moderno con Python e Selenium
Pubblicato: 2022-03-11Il web scraping è stato utilizzato per estrarre dati dai siti Web quasi dal momento in cui è nato il World Wide Web. All'inizio, lo scraping veniva eseguito principalmente su pagine statiche, quelle con elementi, tag e dati noti.
Più recentemente, tuttavia, le tecnologie avanzate nello sviluppo web hanno reso il compito un po' più difficile. In questo articolo, esploreremo come procedere per lo scraping dei dati nel caso in cui la nuova tecnologia e altri fattori impediscano lo scraping standard.
Scraping tradizionale dei dati
Poiché la maggior parte dei siti Web produce pagine destinate alla leggibilità umana piuttosto che alla lettura automatizzata, il web scraping consisteva principalmente nell'assimilare in modo programmatico i dati di markup di una pagina Web (pensare clic con il pulsante destro del mouse, Visualizza sorgente), quindi rilevare schemi statici in quei dati che consentirebbero il programma per “leggere” varie informazioni e salvarle in un file o in un database.
Se si dovessero trovare i dati del report, spesso i dati sarebbero accessibili passando variabili modulo o parametri con l'URL. Per esempio:
https://www.myreportdata.com?month=12&year=2004&clientid=24823
Python è diventato uno dei linguaggi di scraping web più popolari grazie in parte alle varie librerie web che sono state create per questo. Una libreria popolare, Beautiful Soup, è progettata per estrarre dati da file HTML e XML consentendo la ricerca, la navigazione e la modifica di tag (ad esempio, l'albero di analisi).
Scraping basato su browser
Di recente, ho avuto un progetto di scraping che sembrava piuttosto semplice ed ero completamente preparato a utilizzare lo scraping tradizionale per gestirlo. Ma andando più avanti, ho trovato ostacoli che non potevano essere superati con i metodi tradizionali.
Tre problemi principali mi hanno impedito di utilizzare i miei metodi di scraping standard:
- Certificato. Era necessario installare un certificato per accedere alla parte del sito Web in cui si trovavano i dati. Accedendo alla pagina iniziale, è apparso un messaggio che mi chiedeva di selezionare il certificato corretto di quelli installati sul mio computer e di fare clic su OK.
- Iframe. Il sito utilizzava iframe, il che ha incasinato il mio normale scraping. Sì, potrei provare a trovare tutti gli URL iframe, quindi creare una mappa del sito, ma sembrava che potesse diventare ingombrante.
- JavaScript. L'accesso ai dati è avvenuto previa compilazione di un form con parametri (es. ID cliente, intervallo di date, ecc.). Normalmente, ignorerei il modulo e passerei semplicemente le variabili del modulo (tramite URL o come variabili del modulo nascoste) alla pagina dei risultati e vedrei i risultati. Ma in questo caso, il modulo conteneva JavaScript, che non mi permetteva di accedere alle variabili del modulo in modo normale.
Quindi, ho deciso di abbandonare i miei metodi tradizionali e cercare un possibile strumento per lo scraping basato su browser. Funzionerebbe in modo diverso dal normale: invece di andare direttamente a una pagina, scaricare l'albero di analisi ed estrarre elementi di dati, dovrei invece "comportarmi come un essere umano" e utilizzare un browser per raggiungere la pagina di cui avevo bisogno, quindi raschiare il dati - aggirando così la necessità di affrontare le barriere citate.
Selenio
In generale, Selenium è noto come framework di test open source per applicazioni Web, che consente agli specialisti del controllo qualità di eseguire test automatici, eseguire riproduzioni e implementare funzionalità di controllo remoto (consentendo molte istanze del browser per test di carico e più tipi di browser). Nel mio caso, questo sembrava potesse essere utile.
Il mio linguaggio di riferimento per lo scraping web è Python, poiché ha librerie ben integrate che generalmente possono gestire tutte le funzionalità richieste. E abbastanza sicuro, esiste una libreria Selenium per Python. Ciò mi consentirebbe di creare un'istanza di un "browser" - Chrome, Firefox, IE, ecc. - quindi fingere di utilizzare io stesso il browser per accedere ai dati che stavo cercando. E se non volessi che il browser appaia effettivamente, potrei creare il browser in modalità "headless", rendendolo invisibile a qualsiasi utente.
Configurazione del progetto
Per iniziare a sperimentare, dovevo impostare il mio progetto e ottenere tutto ciò di cui avevo bisogno. Ho usato una macchina Windows 10 e mi sono assicurato di avere una versione Python relativamente aggiornata (era la v. 3.7.3). Ho creato uno script Python vuoto, quindi ho caricato le librerie che pensavo potessero essere necessarie, utilizzando PIP (programma di installazione del pacchetto per Python) se non avessi già caricato la libreria. Queste sono le principali librerie con cui ho iniziato:
- Richieste (per effettuare richieste HTTP)
- URLLib3 (Gestione URL)
- Bella zuppa (nel caso in cui il selenio non fosse in grado di gestire tutto)
- Selenium (per la navigazione basata su browser)
Ho anche aggiunto alcuni parametri di chiamata allo script (usando la libreria argparse) in modo da poter giocare con vari set di dati, chiamando lo script dalla riga di comando con diverse opzioni. Quelli includevano l'ID cliente, dal mese/anno e dal mese/anno.
Problema 1 – Il certificato
La prima scelta che dovevo fare era quale browser avrei detto a Selenium di usare. Dato che in genere utilizzo Chrome ed è basato sul progetto Chromium open source (utilizzato anche dai browser Edge, Opera e Amazon Silk), ho pensato di provarlo prima.
Sono stato in grado di avviare Chrome nello script aggiungendo i componenti della libreria di cui avevo bisogno, quindi emettendo un paio di semplici comandi:
# Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)
Dal momento che non ho avviato il browser in modalità headless, il browser è effettivamente apparso e ho potuto vedere cosa stava facendo. Mi ha chiesto immediatamente di selezionare un certificato (che avevo installato in precedenza).
Il primo problema da affrontare è stato il certificato. Come selezionare quello giusto e accettarlo per entrare nel sito? Nel mio primo test dello script, ho ricevuto questo messaggio:
Questo non era buono. Non volevo fare clic manualmente sul pulsante OK ogni volta che eseguivo il mio script.
A quanto pare, sono stato in grado di trovare una soluzione alternativa per questo, senza programmazione. Sebbene avessi sperato che Chrome avesse la capacità di passare un nome di certificato all'avvio, quella funzione non esisteva. Tuttavia, Chrome ha la possibilità di selezionare automaticamente un certificato se esiste una determinata voce nel registro di Windows. Puoi impostarlo per selezionare il primo certificato che vede, oppure essere più specifico. Dato che avevo caricato un solo certificato, ho usato il formato generico.
Pertanto, con quel set, quando ho detto a Selenium di avviare Chrome e viene visualizzata una richiesta di certificato, Chrome "Seleziona automaticamente" il certificato e continua.
Problema 2 – Iframe
Ok, ora ero nel sito ed è apparso un modulo che mi chiedeva di digitare l'ID cliente e l'intervallo di date del rapporto.

Esaminando il modulo negli strumenti per sviluppatori (F12), ho notato che il modulo era presentato all'interno di un iframe. Quindi, prima di poter iniziare a compilare il modulo, dovevo "passare" all'iframe corretto in cui esisteva il modulo. Per fare ciò, ho invocato la funzione di passaggio a Selenium, in questo modo:
# Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)
Bene, quindi ora nel frame giusto sono stato in grado di determinare i componenti, compilare il campo ID cliente e selezionare i menu a discesa della data:
# Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)
Problema 3 – JavaScript
L'unica cosa rimasta sul modulo era "fare clic" sul pulsante Trova, in modo da iniziare la ricerca. Questo è stato un po' complicato in quanto il pulsante Trova sembrava essere controllato da JavaScript e non era un normale pulsante di tipo "Invia". Ispezionandolo negli strumenti di sviluppo, ho trovato l'immagine del pulsante e sono stato in grado di ottenerne XPath, facendo clic con il pulsante destro del mouse.
Quindi, armato di queste informazioni, ho trovato l'elemento sulla pagina, quindi ho fatto clic su di esso.
# Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()
E voilà, il modulo è stato inviato e sono apparsi i dati! Ora, potrei semplicemente raschiare tutti i dati nella pagina dei risultati e salvarli come richiesto. O potrei?
Ottenere i dati
In primo luogo, ho dovuto gestire il caso in cui la ricerca non ha trovato nulla. È stato piuttosto semplice. Verrebbe visualizzato un messaggio sul modulo di ricerca senza lasciarlo, qualcosa del tipo "Nessun record trovato". Ho semplicemente cercato quella stringa e mi sono fermato proprio lì se l'ho trovata.
Ma se i risultati arrivavano, i dati venivano presentati in div con un segno più (+) per aprire una transazione e mostrarne tutti i dettagli. Una transazione aperta mostrava un segno meno (-) che una volta cliccato chiudeva il div. Facendo clic su un segno più si chiamerebbe un URL per aprire il suo div e chiuderne uno aperto.
Pertanto, era necessario trovare eventuali segni più sulla pagina, raccogliere l'URL accanto a ciascuno, quindi scorrere ciascuno per ottenere tutti i dati per ogni transazione.
# Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1
Nel codice sopra, i campi che ho recuperato erano il tipo di transazione e lo stato, quindi aggiunti a un conteggio per determinare quante transazioni soddisfano le regole specificate. Tuttavia, avrei potuto recuperare altri campi all'interno dei dettagli della transazione, come data e ora, sottotipo, ecc.
Per questo progetto, il conteggio è stato restituito a un'applicazione chiamante. Tuttavia, esso e altri dati raschiati potrebbero essere stati archiviati anche in un file flat o in un database.
Ulteriori possibili blocchi stradali e soluzioni
Numerosi altri ostacoli potrebbero essere presentati durante lo scraping di siti Web moderni con la propria istanza del browser, ma la maggior parte può essere risolta. Eccone alcuni:
Cercando di trovare qualcosa prima che appaia
Durante la tua navigazione, quante volte ti accorgi di aspettare che venga visualizzata una pagina, a volte per molti secondi? Bene, lo stesso può verificarsi durante la navigazione a livello di codice. Cerchi una classe o un altro elemento – e non c'è!
Fortunatamente, Selenium ha la capacità di attendere fino a quando non vede un determinato elemento e può andare in timeout se l'elemento non appare, in questo modo:
element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))
Superare un Captcha
Alcuni siti utilizzano Captcha o simili per prevenire robot indesiderati (che potrebbero considerarti). Questo può mettere un freno al web scraping e rallentarlo.
Per semplici prompt (come "che cos'è 2 + 3?"), Questi possono generalmente essere letti e calcolati facilmente. Tuttavia, per barriere più avanzate, ci sono librerie che possono aiutare a provare a decifrarlo. Alcuni esempi sono 2Captcha, Death by Captcha e Bypass Captcha.
Modifiche strutturali del sito web
I siti web sono destinati a cambiare, e spesso lo fanno. Ecco perché quando si scrive uno script di scraping, è meglio tenerlo a mente. Dovrai pensare a quali metodi utilizzerai per trovare i dati e quali non utilizzare. Considera le tecniche di corrispondenza parziale, piuttosto che cercare di abbinare un'intera frase. Ad esempio, un sito Web potrebbe modificare un messaggio da "Nessun record trovato" a "Nessun record trovato", ma se la tua corrispondenza è su "Nessun record", dovresti essere a posto. Inoltre, considera se abbinare su XPATH, ID, nome, testo del collegamento, tag o nome della classe o selettore CSS e quale è meno probabile che cambi.
Riepilogo: Python e Selenio
Questa è stata una breve dimostrazione per dimostrare che è possibile eseguire lo scraping di quasi tutti i siti Web, indipendentemente dalle tecnologie utilizzate e dalle complessità coinvolte. Fondamentalmente, se puoi navigare tu stesso nel sito, generalmente può essere raschiato.
Ora, come avvertimento, non significa che ogni sito web debba essere raschiato. Alcuni hanno restrizioni legittime in atto e ci sono stati numerosi casi giudiziari che hanno deciso la legalità dello scraping di determinati siti. D'altra parte, alcuni siti accolgono favorevolmente e incoraggiano il recupero dei dati dal loro sito Web e in alcuni casi forniscono un'API per semplificare le cose.
In ogni caso, è meglio verificare i termini e le condizioni prima di iniziare qualsiasi progetto. Ma se vai avanti, stai certo che puoi portare a termine il lavoro.
Risorse consigliate per il web scraping complesso:
- Scraping Web Python avanzato: procedure consigliate e soluzioni alternative
- Raschiatura fai-da-te scalabile: come costruire ed eseguire raschiatori su larga scala