Attendere fino al caricamento della pagina con Selenium WebDriver per Python


181

Voglio raschiare tutti i dati di una pagina implementati da uno scroll infinito. Il seguente codice Python funziona.

for i in range(100):
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(5)

Ciò significa che ogni volta che scorro verso il basso, devo attendere 5 secondi, il che è generalmente sufficiente affinché la pagina finisca di caricare i contenuti appena generati. Ma questo potrebbe non essere efficiente in termini di tempo. La pagina potrebbe terminare il caricamento dei nuovi contenuti entro 5 secondi. Come posso rilevare se la pagina ha finito di caricare i nuovi contenuti ogni volta che scorro verso il basso? Se riesco a rilevarlo, posso scorrere di nuovo verso il basso per vedere più contenuti una volta che avrò terminato il caricamento della pagina. Questo è più efficiente in termini di tempo.


1
Potrebbe essere utile sapere qualcosa in più sulla pagina. Gli elementi sono sequenziali o prevedibili? Puoi attendere il caricamento degli elementi controllando la visibilità usando id o xpath
user2272115

Sto eseguendo la scansione della seguente pagina: pinterest.com/cremedelacrumb/yum
apogne

1
possibile duplicato di
Rilevamento affidabile del

Questo risponde alla tua domanda? Attendere il caricamento della pagina in Selenio
Matej J

Risposte:


234

Il webdriverattenderà per una pagina a carico di default tramite .get()il metodo.

Dato che potresti cercare un elemento specifico come diceva @ user227215, dovresti usare WebDriverWaitper attendere un elemento situato nella tua pagina:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException

browser = webdriver.Firefox()
browser.get("url")
delay = 3 # seconds
try:
    myElem = WebDriverWait(browser, delay).until(EC.presence_of_element_located((By.ID, 'IdOfMyElement')))
    print "Page is ready!"
except TimeoutException:
    print "Loading took too much time!"

L'ho usato per controllare gli avvisi. È possibile utilizzare qualsiasi altro metodo di tipo per trovare il localizzatore.

MODIFICA 1:

Devo dire che webdriveraspetterà il caricamento di una pagina per impostazione predefinita. Non attende il caricamento all'interno di frame o richieste Ajax. Ciò significa che quando si utilizza .get('url'), il browser attenderà fino al completo caricamento della pagina, quindi passerà al comando successivo nel codice. Ma quando pubblichi una richiesta Ajax, webdrivernon aspetta ed è tua responsabilità attendere un periodo di tempo adeguato per il caricamento della pagina o di una parte della pagina; quindi c'è un modulo chiamato expected_conditions.


3
Stavo ottenendo "l'argomento find_element () dopo * deve essere una sequenza, non WebElement" modificato in "WebDriverWait (browser, delay) .until (EC.presence_of_element_located ((By.ID," IdOfMyElement ")))" vedi il selenio
fragles

2
Il commento di @fragles e la risposta di David Cullen sono stati ciò che ha funzionato per me. Forse questa risposta accettata potrebbe essere aggiornata di conseguenza?
Michael Ohlrogge,

6
Il passaggio browser.find_element_by_id('IdOfMyElement')provoca l' NoSuchElementExceptionaumento di a. La documentazione dice di passare una tupla che assomiglia a questo: (By.ID, 'IdOfMyElement'). Vedi la mia risposta
David Cullen il

2
Spero che questo aiuti qualcun altro perché inizialmente non mi era chiaro: WebDriverWait restituirà effettivamente un oggetto Web su cui puoi quindi eseguire un'azione (ad esempio click()), leggere il testo ecc. Avevo l'impressione sbagliata che ha causato un'attesa, dopo di che dovevi ancora trovare l'elemento. Se fai un'attesa, poi un elemento find in seguito, il selenio verrà fuori errore perché tenta di trovare l'elemento mentre la vecchia attesa è ancora in elaborazione (si spera che abbia senso). La linea di fondo è che non è necessario trovare l'elemento dopo aver utilizzato WebDriverWait: è già un oggetto.
Ben Wilson,

1
@Gopgop Wow questo è così brutto non è un commento costruttivo. Cosa c'è di brutto al riguardo? Come potrebbe essere migliorato?
Modus Tollens,

73

Il tentativo di passare find_element_by_idal costruttore per presence_of_element_located(come mostrato nella risposta accettata ) è NoSuchElementExceptionstato sollevato. Ho dovuto usare la sintassi nel commento dei fragles :

from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

driver = webdriver.Firefox()
driver.get('url')
timeout = 5
try:
    element_present = EC.presence_of_element_located((By.ID, 'element_id'))
    WebDriverWait(driver, timeout).until(element_present)
except TimeoutException:
    print "Timed out waiting for page to load"

Questo corrisponde all'esempio nella documentazione . Ecco un link alla documentazione di By .


2
Grazie! sì, questo era necessario anche per me. ID non è l'unico attributo che può essere utilizzato, per ottenere l'elenco completo, utilizzare la guida (By). Ad esempio, ho usatoEC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge

Funziona così anche per me! Ho scritto una risposta aggiuntiva espandendo i diversi localizzatori disponibili con l' Byoggetto.
J0ANMM,

Ho postato una domanda follow trattare con le aspettative in cui possono essere caricati pagine diverse, e non sempre la stessa pagina: stackoverflow.com/questions/51641546/...
Liquidgenius

48

Trova di seguito 3 metodi:

readyState

Verifica pagina readyState (non affidabile):

def page_has_loaded(self):
    self.log.info("Checking if {} page is loaded.".format(self.driver.current_url))
    page_state = self.driver.execute_script('return document.readyState;')
    return page_state == 'complete'

La wait_forfunzione di supporto è buona, ma sfortunatamente click_through_to_new_pageè aperta alle condizioni di gara in cui riusciamo a eseguire lo script nella vecchia pagina, prima che il browser abbia iniziato a elaborare il clic e page_has_loadedrestituisca immediatamente il valore.

id

Confronto di nuovi ID pagina con quello vecchio:

def page_has_loaded_id(self):
    self.log.info("Checking if {} page is loaded.".format(self.driver.current_url))
    try:
        new_page = browser.find_element_by_tag_name('html')
        return new_page.id != old_page.id
    except NoSuchElementException:
        return False

È possibile che il confronto degli ID non sia efficace quanto l'attesa di eccezioni di riferimento non aggiornate.

staleness_of

Utilizzando il staleness_ofmetodo:

@contextlib.contextmanager
def wait_for_page_load(self, timeout=10):
    self.log.debug("Waiting for page to load at {}.".format(self.driver.current_url))
    old_page = self.find_element_by_tag_name('html')
    yield
    WebDriverWait(self, timeout).until(staleness_of(old_page))

Per maggiori dettagli, consulta il blog di Harry .


Perché dici che self.driver.execute_script('return document.readyState;')non è affidabile? Sembra funzionare perfettamente per il mio caso d'uso, che è in attesa del caricamento di un file statico in una nuova scheda (che viene aperta tramite javascript in un'altra scheda anziché .get ()).
Arthur Hebert,

1
@ArthurHebert Potrebbe non essere affidabile a causa delle condizioni di gara, ho aggiunto la citazione pertinente.
Kenorb,

23

Come menzionato nella risposta di David Cullen , ho sempre visto dei consigli su come usare una linea come la seguente:

element_present = EC.presence_of_element_located((By.ID, 'element_id'))
WebDriverWait(driver, timeout).until(element_present)

È stato difficile per me trovare da qualche parte tutti i possibili localizzatori che possono essere utilizzati con il By, quindi ho pensato che sarebbe stato utile fornire l'elenco qui. Secondo Web Scraping with Python di Ryan Mitchell:

ID

Usato nell'esempio; trova gli elementi in base al loro attributo ID HTML

CLASS_NAME

Utilizzato per trovare elementi in base all'attributo della loro classe HTML. Perché questa funzione CLASS_NAMEnon è semplicemente CLASS? L'uso del modulo object.CLASS creerebbe problemi per la libreria Java di Selenium, dove .classè un metodo riservato. Al fine di mantenere coerente la sintassi del selenio tra lingue diverse, è CLASS_NAMEstata invece utilizzata.

CSS_SELECTOR

Reperti elementi per la loro classe, id, o il nome di tag, utilizzando il #idName, .className, tagNameconvention.

LINK_TEXT

Trova i tag HTML in base al testo che contengono. Ad esempio, un collegamento che dice "Avanti" può essere selezionato usando (By.LINK_TEXT, "Next").

PARTIAL_LINK_TEXT

Simile a LINK_TEXT, ma corrisponde a una stringa parziale.

NAME

Trova i tag HTML in base all'attributo name. Questo è utile per i moduli HTML.

TAG_NAME

Trova i tag HTML in base al nome del tag.

XPATH

Utilizza un'espressione XPath ... per selezionare gli elementi corrispondenti.


5
La documentazione per By elenca gli attributi che possono essere utilizzati come localizzatori.
David Cullen,

1
Era quello che stavo cercando! Grazie! Bene, ora dovrebbe essere più facile da trovare poiché Google mi stava inviando a questa domanda, ma non alla documentazione ufficiale.
J0ANMM,

Grazie per la citazione dal libro. È molto più chiaro della documentazione.
ZygD


11

In una nota a margine, invece di scorrere verso il basso 100 volte, puoi verificare se non ci sono più modifiche al DOM (siamo nel caso in cui la parte inferiore della pagina sia AJAX lazy-upload)

def scrollDown(driver, value):
    driver.execute_script("window.scrollBy(0,"+str(value)+")")

# Scroll down the page
def scrollDownAllTheWay(driver):
    old_page = driver.page_source
    while True:
        logging.debug("Scrolling loop")
        for i in range(2):
            scrollDown(driver, 500)
            time.sleep(2)
        new_page = driver.page_source
        if new_page != old_page:
            old_page = new_page
        else:
            break
    return True

Questo è utile Tuttavia, cosa rappresenta la 500? È abbastanza grande per arrivare alla fine della pagina?
Moondra,

È la quantità che la pagina dovrebbe scorrere ... dovresti impostarla il più in alto possibile. Ho appena scoperto che questo numero era abbastanza per me, dal momento che fa scorrere la pagina fino in fondo fino a quando gli elementi AJAX sono caricati in modo pigro, spronando la necessità di ricaricare di nuovo la pagina
raffaem,

Questo aiuta quando si cerca di assicurarsi che tutti i commenti su un problema in gitlab siano completamente caricati.
bgStack15,

7

Hai provato driver.implicitly_wait. È come un'impostazione per il driver, quindi la chiami solo una volta nella sessione e in pratica dice al driver di attendere un determinato periodo di tempo fino a quando ogni comando può essere eseguito.

driver = webdriver.Chrome()
driver.implicitly_wait(10)

Quindi, se si imposta un tempo di attesa di 10 secondi, eseguirà il comando il più presto possibile, attendendo 10 secondi prima di arrendersi. L'ho usato in scenari di scorrimento simili, quindi non vedo perché non funzionerebbe nel tuo caso. Spero sia utile.

Per poter risolvere questa risposta, devo aggiungere un nuovo testo. Assicurati di usare una "w" minuscola in implicitly_wait.


Qual'è la differenza tra wait implicitamente e webdriverwait?
song0089,

4

Che ne dici di mettere WebDriverWait nel ciclo While e catturare le eccezioni.

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

browser = webdriver.Firefox()
browser.get("url")
delay = 3 # seconds
while True:
    try:
        WebDriverWait(browser, delay).until(EC.presence_of_element_located(browser.find_element_by_id('IdOfMyElement')))
        print "Page is ready!"
        break # it will break from the loop once the specific element will be present. 
    except TimeoutException:
        print "Loading took too much time!-Try again"

non hai bisogno del ciclo?
Corey Goldberg,

4

Qui l'ho fatto usando una forma piuttosto semplice:

from selenium import webdriver
browser = webdriver.Firefox()
browser.get("url")
searchTxt=''
while not searchTxt:
    try:    
      searchTxt=browser.find_element_by_name('NAME OF ELEMENT')
      searchTxt.send_keys("USERNAME")
    except:continue

1

Puoi farlo in modo molto semplice con questa funzione:

def page_is_loading(driver):
    while True:
        x = driver.execute_script("return document.readyState")
        if x == "complete":
            return True
        else:
            yield False

e quando vuoi fare qualcosa dopo aver completato il caricamento della pagina, puoi usare:

Driver = webdriver.Firefox(options=Options, executable_path='geckodriver.exe')
Driver.get("https://www.google.com/")

while not page_is_loading(Driver):
    continue

Driver.execute_script("alert('page is loaded')")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.