Scraping web modern cu Python și Selenium
Publicat: 2022-03-11Web scraping a fost folosit pentru a extrage date de pe site-uri web aproape din momentul în care sa născut World Wide Web. În primele zile, scrapingul se făcea în principal pe pagini statice – cele cu elemente, etichete și date cunoscute.
Mai recent, însă, tehnologiile avansate în dezvoltarea web au făcut sarcina ceva mai dificilă. În acest articol, vom explora modul în care am putea proceda la răzuirea datelor în cazul în care tehnologia nouă și alți factori împiedică scrapingul standard.
Scraping tradițional de date
Deoarece majoritatea site-urilor web produc pagini destinate citirii umane mai degrabă decât citirii automate, web scraping a constat în principal în digerarea programatică a datelor de marcare a unei pagini web (gândiți-vă că faceți clic dreapta, Vizualizare sursă), apoi detectarea modelelor statice în acele date care ar permite programului pentru a „citi” diverse informații și a le salva într-un fișier sau o bază de date.
Dacă ar fi găsite date din raport, de multe ori, datele ar fi accesibile prin trecerea fie a variabilelor de formular, fie a parametrilor cu adresa URL. De exemplu:
https://www.myreportdata.com?month=12&year=2004&clientid=24823
Python a devenit unul dintre cele mai populare limbaje web scraping datorită, în parte, diverselor biblioteci web care au fost create pentru acesta. O bibliotecă populară, Beautiful Soup, este concepută pentru a extrage date din fișierele HTML și XML, permițând căutarea, navigarea și modificarea etichetelor (adică, arborele de analiză).
Scraping bazat pe browser
Recent, am avut un proiect de scraping care mi s-a părut destul de simplu și eram pe deplin pregătit să folosesc scrapingul tradițional pentru a-l gestiona. Dar, pe măsură ce am intrat mai departe, am găsit obstacole care nu puteau fi depășite cu metodele tradiționale.
Trei probleme principale m-au împiedicat să utilizez metodele mele standard de răzuire:
- Certificat. Era necesar să fie instalat un certificat pentru a accesa porțiunea site-ului web unde se aflau datele. La accesarea paginii inițiale, a apărut o solicitare prin care mi-a cerut să selectez certificatul corespunzător al celor instalate pe computerul meu și să dau clic pe OK.
- Iframe. Site-ul a folosit iframe, care mi-au încurcat scrapingul normal. Da, aș putea încerca să găsesc toate adresele URL iframe, apoi să construiesc un sitemap, dar asta părea că ar putea deveni greu de utilizat.
- JavaScript. Datele au fost accesate după completarea unui formular cu parametri (de exemplu, ID client, interval de date etc.). În mod normal, aș ocoli formularul și aș trece pur și simplu variabilele de formular (prin URL sau ca variabile de formular ascunse) la pagina de rezultate și aș vedea rezultatele. Dar în acest caz, formularul conținea JavaScript, ceea ce nu mi-a permis să accesez variabilele formularului într-un mod normal.
Așadar, am decis să renunț la metodele mele tradiționale și să mă uit la un posibil instrument de scraping bazat pe browser. Acest lucru ar funcționa diferit decât în mod normal – în loc să merg direct la o pagină, să descarc arborele de analiză și să scot elemente de date, aș „acționa ca un om” și aș folosi un browser pentru a ajunge la pagina de care aveam nevoie, apoi aș răzui date – astfel, ocolind necesitatea de a face față barierelor menționate.
Seleniu
În general, Selenium este binecunoscut ca un cadru de testare open-source pentru aplicații web – permițând specialiștilor QA să efectueze teste automate, să execute redări și să implementeze funcționalitatea de control de la distanță (permițând multe instanțe de browser pentru testarea încărcării și mai multe tipuri de browser). În cazul meu, acest lucru părea că ar putea fi util.
Limbajul meu de bază pentru web scraping este Python, deoarece are biblioteci bine integrate care pot gestiona, în general, toate funcționalitățile necesare. Și, desigur, există o bibliotecă Selenium pentru Python. Acest lucru mi-ar permite să instanțiez un „browser” – Chrome, Firefox, IE etc. – apoi să pretind că folosesc eu însumi browserul pentru a avea acces la datele pe care le căutam. Și dacă nu aș vrea ca browserul să apară efectiv, aș putea crea browserul în modul „fără cap”, făcându-l invizibil pentru orice utilizator.
Configurarea proiectului
Pentru a începe să experimentez, trebuia să-mi configurez proiectul și să obțin tot ce aveam nevoie. Am folosit o mașină cu Windows 10 și m-am asigurat că am o versiune Python relativ actualizată (era v. 3.7.3). Am creat un script Python gol, apoi am încărcat bibliotecile pe care le credeam că ar fi necesare, folosind PIP (instalator de pachete pentru Python) dacă nu aveam deja biblioteca încărcată. Acestea sunt principalele biblioteci cu care am început:
- Solicitări (pentru efectuarea de solicitări HTTP)
- URLLib3 (tratare URL)
- Supă frumoasă (în cazul în care seleniul nu s-ar descurca cu totul)
- Selenium (pentru navigare bazată pe browser)
Am adăugat, de asemenea, câțiva parametri de apelare la script (folosind biblioteca argparse), astfel încât să mă pot juca cu diverse seturi de date, apelând scriptul din linia de comandă cu diferite opțiuni. Acestea au inclus ID-ul clientului, de la lună/an și până la lună/an.
Problema 1 – Certificatul
Prima alegere pe care trebuia să o fac a fost ce browser aveam să-i spun lui Selenium să folosesc. Deoarece folosesc în general Chrome și este construit pe proiectul Chromium cu sursă deschisă (utilizat și de browserele Edge, Opera și Amazon Silk), m-am gândit că voi încerca asta mai întâi.
Am reușit să pornesc Chrome în script adăugând componentele bibliotecii de care aveam nevoie, apoi lansând câteva comenzi simple:
# 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)
Deoarece nu am lansat browserul în modul headless, browserul a apărut de fapt și am putut vedea ce face. Mi-a cerut imediat să selectez un certificat (pe care îl instalasem mai devreme).
Prima problemă de rezolvat a fost certificatul. Cum îl selectez pe cel potrivit și îl acceptăm pentru a intra pe site? La primul meu test al scriptului, am primit acest prompt:
Asta nu a fost bine. Nu am vrut să dau clic manual pe butonul OK de fiecare dată când am rulat scriptul.
După cum se dovedește, am putut găsi o soluție pentru aceasta - fără programare. Deși am sperat că Chrome are capacitatea de a transmite un nume de certificat la pornire, această caracteristică nu exista. Cu toate acestea, Chrome are capacitatea de a selecta automat un certificat dacă există o anumită intrare în registrul Windows. Îl puteți seta să selecteze primul certificat pe care îl vede sau să fie mai specific. Deoarece aveam un singur certificat încărcat, am folosit formatul generic.
Astfel, cu acel set, când i-am spus lui Selenium să lanseze Chrome și a apărut o solicitare de certificat, Chrome a „Selectat automat” certificatul și a continuat.
Problema 2 – Iframes
Bine, deci acum eram pe site și a apărut un formular care mă îndemna să introduc ID-ul clientului și intervalul de date al raportului.
Examinând formularul din instrumentele pentru dezvoltatori (F12), am observat că formularul a fost prezentat într-un iframe. Așadar, înainte de a putea începe să completez formularul, trebuia să „trec” la iframe-ul potrivit unde exista formularul. Pentru a face acest lucru, am invocat funcția de comutare a Selenium, astfel:

# Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)
Bine, așa că acum, în cadrul potrivit, am putut să determin componentele, să completez câmpul ID client și să selectez meniurile derulante pentru dată:
# 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
Singurul lucru rămas în formular a fost să „faceți clic” pe butonul Găsiți, astfel încât să înceapă căutarea. Acest lucru a fost puțin complicat, deoarece butonul Găsiți părea să fie controlat de JavaScript și nu era un buton normal de tip „Trimite”. Inspectând-o în instrumentele pentru dezvoltatori, am găsit imaginea butonului și am putut obține XPath-ul acesteia, făcând clic dreapta.
Apoi, înarmat cu aceste informații, am găsit elementul pe pagină, apoi am dat clic pe el.
# 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()
Și voilà, s-a depus formularul și au apărut datele! Acum, aș putea doar să răzuiesc toate datele de pe pagina de rezultate și să le salvez după cum este necesar. Sau aș putea?
Obținerea datelor
Mai întâi, a trebuit să mă ocup de cazul în care căutarea nu a găsit nimic. A fost destul de simplu. Ar afișa un mesaj pe formularul de căutare fără a-l părăsi, ceva de genul „Nu au fost găsite înregistrări”. Pur și simplu am căutat acel șir și m-am oprit chiar acolo dacă l-am găsit.
Dar dacă au venit rezultatele, datele erau prezentate în div-uri cu semnul plus (+) pentru a deschide o tranzacție și a arăta toate detaliile acesteia. O tranzacție deschisă arăta un semn minus (-) care atunci când făcea clic ar închide div-ul. Făcând clic pe un semn plus, ar apela o adresă URL pentru a-și deschide div-ul și a închide oricare dintre cele deschise.
Astfel, a fost necesar să găsiți semnele plus pe pagină, să adune URL-ul lângă fiecare, apoi să treceți prin fiecare pentru a obține toate datele pentru fiecare tranzacție.
# 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
În codul de mai sus, câmpurile pe care le-am preluat au fost tipul tranzacției și starea, apoi adăugate la un număr pentru a determina câte tranzacții se potrivesc regulilor care au fost specificate. Cu toate acestea, aș fi putut prelua și alte câmpuri din detaliile tranzacției, cum ar fi data și ora, subtipul etc.
Pentru acest proiect, numărul a fost returnat înapoi la o aplicație de apelare. Cu toate acestea, acesta și alte date răzuite ar fi putut fi stocate într-un fișier plat sau într-o bază de date, de asemenea.
Blocaje și soluții posibile suplimentare
S-ar putea să apară numeroase alte obstacole în timpul răzuirii site-urilor web moderne cu propria instanță de browser, dar cele mai multe pot fi rezolvate. Iată câteva:
Încerc să găsesc ceva înainte să apară
În timp ce vă răsfoiți, cât de des descoperiți că așteptați să apară o pagină, uneori pentru mai multe secunde? Ei bine, același lucru se poate întâmpla în timpul navigării în mod programatic. Cauți o clasă sau alt element – și nu există!
Din fericire, Selenium are capacitatea de a aștepta până când vede un anumit element și poate expira dacă elementul nu apare, așa:
element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))
Trecând printr-un Captcha
Unele site-uri folosesc Captcha sau similar pentru a preveni roboții nedoriți (pe care s-ar putea să vă considere). Acest lucru poate afecta răzuirea web și poate încetini mult.
Pentru solicitări simple (cum ar fi „ce înseamnă 2 + 3?”), acestea pot fi, în general, citite și înțelese cu ușurință. Cu toate acestea, pentru bariere mai avansate, există biblioteci care pot ajuta la încercarea de a le sparge. Câteva exemple sunt 2Captcha, Death by Captcha și Bypass Captcha.
Modificări structurale site-ului
Site-urile web sunt menite să se schimbe – și o fac adesea. De aceea, atunci când scrieți un script scraping, cel mai bine este să aveți în vedere acest lucru. Veți dori să vă gândiți ce metode veți folosi pentru a găsi datele și pe care să nu le utilizați. Luați în considerare tehnici de potrivire parțială, mai degrabă decât să încercați să potriviți o frază întreagă. De exemplu, un site web ar putea schimba un mesaj din „Nu au fost găsite înregistrări” în „Nu au fost găsite înregistrări” – dar dacă potrivirea dvs. este pe „Fără înregistrări”, ar trebui să fiți în regulă. De asemenea, luați în considerare dacă să se potrivească pe XPATH, ID, nume, text link, etichetă sau nume de clasă sau selector CSS - și care este cel mai puțin probabil să se schimbe.
Rezumat: Python și Selenium
Aceasta a fost o scurtă demonstrație pentru a arăta că aproape orice site web poate fi răzuit, indiferent de tehnologiile utilizate și de complexitățile implicate. Practic, dacă puteți naviga singur pe site, acesta poate fi în general răzuit.
Acum, ca avertisment, nu înseamnă că fiecare site ar trebui să fie eliminat. Unele au restricții legitime în vigoare și au existat numeroase cauze în instanță care au decis legalitatea răzuirii anumitor site-uri. Pe de altă parte, unele site-uri salută și încurajează preluarea datelor de pe site-ul lor web și, în unele cazuri, oferă un API pentru a ușura lucrurile.
În orice caz, cel mai bine este să verificați termenii și condițiile înainte de a începe orice proiect. Dar dacă mergi mai departe, fii sigur că poți duce treaba la bun sfârșit.
Resurse recomandate pentru web scraping complex:
- Scraping avansat Python Web: Cele mai bune practici și soluții
- Răzuire scalabilă de tip do-it-yourself: Cum să construiți și să rulați răzuitoare pe scară largă