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.

apogne
fuente
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.

Zeinab Abbasimazar
fuente
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 .

David Cullen
fuente
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 .

kenorb
fuente
¿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.

J0ANMM
fuente
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
raffaem
fuente
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.

seeiespi
fuente
¿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"
Rao
fuente
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
ahmed abdelmalek
fuente
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')")
NaabNuts
fuente