Espere hasta que la página se cargue con Selenium WebDriver para Python


181

Quiero raspar todos los datos de una página implementada por un desplazamiento infinito. El siguiente código de Python funciona.

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

Esto significa que cada vez que me desplazo hacia abajo, necesito esperar 5 segundos, que generalmente es suficiente para que la página termine de cargar los contenidos recién generados. Pero, esto puede no ser eficiente en el tiempo. La página puede terminar de cargar los nuevos contenidos en 5 segundos. ¿Cómo puedo detectar si la página terminó de cargar los nuevos contenidos cada vez que me desplazo hacia abajo? Si puedo detectar esto, puedo desplazarme hacia abajo nuevamente para ver más contenido una vez que sepa que la página terminó de cargarse. Esto es más eficiente en el tiempo.


1
Puede ser útil saber un poco más sobre la página. ¿Los elementos son secuenciales o predecibles? Puede esperar a que se carguen los elementos comprobando la visibilidad utilizando id o xpath
user2272115

Estoy rastreando la siguiente página: pinterest.com/cremedelacrumb/yum
apogne


¿Responde esto a tu pregunta? Espere a que se cargue la página en Selenium
Matej J

Respuestas:


234

El webdriverva a esperar a que cargue una página por defecto a través de .get()método.

Como puede estar buscando algún elemento específico como dijo @ user227215, debe usar WebDriverWaitpara esperar un elemento ubicado en su página:

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!"

Lo he usado para verificar alertas. Puede utilizar cualquier otro tipo de método para encontrar el localizador.

EDITAR 1:

Debo mencionar que webdriveresperará a que se cargue una página por defecto. No espera la carga dentro de los marcos o las solicitudes de ajax. Significa que cuando lo use .get('url'), su navegador esperará hasta que la página esté completamente cargada y luego irá al siguiente comando en el código. Pero cuando publica una solicitud de ajax, webdriverno espera y es su responsabilidad esperar la cantidad de tiempo adecuada para que se cargue la página o parte de ella; entonces hay un módulo llamado expected_conditions.


3
Estaba obteniendo el argumento "find_element () después de * debe ser una secuencia, no WebElement" cambiado a "WebDriverWait (navegador, retraso) .until (EC.presence_of_element_located ((By.ID," IdOfMyElement ")))" ver manual selenium- python.readthedocs.org/en/latest/waits.html
fragmentos

2
El comentario de @fragles y la respuesta de David Cullen fueron lo que funcionó para mí. ¿Quizás esta respuesta aceptada podría actualizarse en consecuencia?
Michael Ohlrogge

66
Pasando browser.find_element_by_id('IdOfMyElement')provoca un NoSuchElementExceptionser levantado. La documentación dice que pasar una tupla que tiene este aspecto: (By.ID, 'IdOfMyElement'). Mira mi respuesta
David Cullen

2
Espero que esto ayude a alguien más porque inicialmente no estaba claro para mí: WebDriverWait en realidad devolverá un objeto web en el que luego puede realizar una acción (por ejemplo click()), leer texto, etc. Tenía la impresión errónea de que solo provocó una espera, después de lo cual aún tenía que encontrar el elemento. Si hace una espera, luego se encuentra un elemento de búsqueda, el selenio generará un error porque intenta encontrar el elemento mientras la espera anterior aún se está procesando (con suerte, eso tiene sentido). La conclusión es que no necesita encontrar el elemento después de usar WebDriverWait: ya es un objeto.
Ben Wilson

1
@Gopgop Wow, esto es tan feo no es un comentario constructivo. ¿Qué tiene de feo? ¿Cómo podría mejorarse?
Modus Tollens

73

Intentando pasar find_element_by_idal constructor por presence_of_element_located(como se muestra en la respuesta aceptada ) provocó NoSuchElementExceptionque se elevara. Tuve que usar la sintaxis en el comentario de los fragmentos :

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"

Esto coincide con el ejemplo en la documentación . Aquí hay un enlace a la documentación de By .


2
¡Gracias! Sí, esto también era necesario para mí. La identificación no es el único atributo que se puede usar, para obtener la lista completa, use la ayuda (Por). Por ejemplo, solíaEC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge

¡Así es como funciona para mí también! Escribí una respuesta adicional ampliando los diferentes localizadores que están disponibles con el Byobjeto.
J0ANMM

He publicado una pregunta de seguimiento sobre expectativas en las que se pueden cargar diferentes páginas y no siempre la misma página: stackoverflow.com/questions/51641546/…
Liquidgenius

48

Encuentra a continuación 3 métodos:

readyState

Página de comprobación readyState (no confiable):

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_forfunción auxiliar es buena, pero desafortunadamente click_through_to_new_pageestá abierta a la condición de carrera en la que logramos ejecutar el script en la página anterior, antes de que el navegador haya comenzado a procesar el clic, y page_has_loadedsimplemente se vuelve verdadero de inmediato.

id

Comparando nuevos identificadores de página con el anterior:

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

Es posible que comparar identificadores no sea tan efectivo como esperar excepciones de referencia obsoletas.

staleness_of

Utilizando el staleness_ofmétodo:

@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))

Para más detalles, consulte el blog de Harry .


¿Por qué dices que self.driver.execute_script('return document.readyState;')no es confiable? Parece que funciona perfectamente para mi caso de uso, que está esperando que se cargue un archivo estático en una nueva pestaña (que se abre a través de JavaScript en otra pestaña en lugar de .get ()).
Arthur Hebert el

1
@ArthurHebert Podría no ser confiable debido a la condición de la carrera, agregué una cita relevante.
kenorb

23

Como se menciona en la respuesta de David Cullen , siempre he visto recomendaciones para usar una línea como la siguiente:

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

Me resultó difícil encontrar en algún lugar todos los posibles localizadores que se puedan utilizar con el By, por lo que pensé que sería útil proporcionar la lista aquí. Según Web Scraping with Python de Ryan Mitchell:

ID

Usado en el ejemplo; encuentra elementos por su atributo de identificación HTML

CLASS_NAME

Se usa para buscar elementos por su atributo de clase HTML. ¿Por qué esta función CLASS_NAMEno es simplemente CLASS? Usar el formulario object.CLASS crearía problemas para la biblioteca Java de Selenium, donde .classes un método reservado. Con el fin de mantener la sintaxis de Selenium consistente entre diferentes idiomas, CLASS_NAMEse utilizó en su lugar.

CSS_SELECTOR

Encuentra elementos por su clase, ID o nombre de la etiqueta, con el #idName, .className, tagNameconvención.

LINK_TEXT

Encuentra etiquetas HTML por el texto que contienen. Por ejemplo, un enlace que dice "Siguiente" se puede seleccionar usando (By.LINK_TEXT, "Next").

PARTIAL_LINK_TEXT

Similar a LINK_TEXT, pero coincide en una cadena parcial.

NAME

Encuentra etiquetas HTML por su atributo de nombre. Esto es útil para formularios HTML.

TAG_NAME

Encuentra etiquetas HTML por su nombre de etiqueta.

XPATH

Utiliza una expresión XPath ... para seleccionar elementos coincidentes.


55
La documentación de By enumera los atributos que se pueden usar como localizadores.
David Cullen

1
¡Eso era lo que había estado buscando! ¡Gracias! Bueno, ahora debería ser más fácil de encontrar ya que google me estaba enviando a esta pregunta, pero no a la documentación oficial.
J0ANMM

Gracias por la cita del libro. Es mucho más claro que la documentación.
ZygD


11

En una nota al margen, en lugar de desplazarse hacia abajo 100 veces, puede verificar si no hay más modificaciones en el DOM (estamos en el caso de que la parte inferior de la página esté cargada AJAX)

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

Esto es útil. Sin embargo, ¿qué representa el 500? ¿Es lo suficientemente grande como para llegar al final de la página?
Moondra

Es la cantidad que la página debe desplazarse ... debe configurarla lo más alto posible. Me acabo de enterar que este número era suficiente para mí, ya que hace que el desplazamiento página hasta la parte inferior hasta que los elementos de AJAX son perezosa-cargado, estimulando la necesidad de volver a cargar la página
raffaem

Esto ayuda al tratar de garantizar que todos los comentarios sobre un problema en gitlab estén completamente cargados.
bgStack15

7

¿Lo has intentado driver.implicitly_wait? Es como una configuración para el controlador, por lo que solo se llama una vez en la sesión y básicamente le dice al controlador que espere la cantidad de tiempo dada hasta que se pueda ejecutar cada comando.

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

Entonces, si establece un tiempo de espera de 10 segundos, ejecutará el comando lo antes posible, esperando 10 segundos antes de que se dé por vencido. He usado esto en escenarios similares de desplazamiento hacia abajo, así que no veo por qué no funcionaría en su caso. Espero que esto sea útil.

Para poder corregir esta respuesta, tengo que agregar un nuevo texto. Asegúrese de usar una 'w' minúscula implicitly_wait.


¿Cuál es la diferencia entre implícitamente esperar y webdriverwait?
song0089

4

¿Qué hay de poner WebDriverWait en el bucle While y capturar las excepciones?

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"

no necesitas el bucle?
Corey Goldberg

4

Aquí lo hice usando una forma bastante simple:

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

Puede hacerlo muy simple con esta función:

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

y cuando desee hacer algo después de completar la carga de la página, puede usar:

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')")
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.