web-dev-qa-db-fra.com

Attendez que la page soit chargée avec Selenium WebDriver pour Python

Je veux gratter toutes les données d'une page implémentée par un défilement infini. Le code python suivant fonctionne.

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

Cela signifie que chaque fois que je fais défiler l'écran jusqu'en bas, je dois attendre 5 secondes, ce qui est généralement suffisant pour que la page finisse de charger le contenu nouvellement généré. Mais, cela peut ne pas être efficace du temps. La page peut finir de charger le nouveau contenu dans les 5 secondes. Comment savoir si la page a fini de charger le nouveau contenu à chaque défilement? Si je peux détecter cela, je peux faire défiler à nouveau pour voir plus de contenu une fois que je sais que la page a été chargée. C'est plus efficace en temps.

125
apogne

La webdriver attendra qu'une page se charge par défaut via la méthode .get().

Comme vous êtes peut-être à la recherche d'un élément spécifique, comme @ user227215 l'a indiqué, vous devez utiliser WebDriverWait pour attendre un élément situé dans votre page:

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

Je l'ai utilisé pour vérifier les alertes. Vous pouvez utiliser n'importe quelle autre méthode de type pour trouver le localisateur.

EDIT 1:

Je devrais mentionner que la webdriver attendra le chargement d’une page par défaut. Il n'attend pas le chargement à l'intérieur des cadres ni les requêtes ajax. Cela signifie que lorsque vous utilisez .get('url'), votre navigateur attendra que la page soit complètement chargée, puis passera à la commande suivante du code. Mais lorsque vous publiez une demande ajax, webdriver n'attend pas et il vous incombe d'attendre le délai approprié pendant le chargement de la page ou d'une partie de la page. il existe donc un module nommé expected_conditions.

161
Zeinab Abbasimazar

Essayer de passer find_element_by_id au constructeur pour presence_of_element_located (comme indiqué dans le réponse acceptée ) a provoqué le déclenchement de NoSuchElementException. Je devais utiliser la syntaxe dans fragles ' comment :

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"

Cela correspond à exemple dans la documentation . Voici un lien vers le documentation pour By .

57
David Cullen

Trouvez ci-dessous 3 méthodes:

état prêt

Vérification de la page readyState (non fiable):

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 fonction d'assistance wait_for est bonne, mais malheureusement click_through_to_new_page est ouverte à la situation de concurrence critique dans laquelle nous parvenons à exécuter le script dans l'ancienne page avant que le navigateur n'ait commencé à traiter le clic et page_has_loaded revient juste vrai tout de suite.

id

Comparer les nouveaux identifiants de page avec l'ancien:

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

Il est possible que la comparaison d'identifiants ne soit pas aussi efficace que d'attendre des exceptions de références obsolètes.

staleness_of

Utilisation de la méthode staleness_of:

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

Pour plus de détails, consultez le blog de Harry .

32
kenorb

De Selenium/webdriver/support/wait.py

driver = ...
from Selenium.webdriver.support.wait import WebDriverWait
element = WebDriverWait(driver, 10).until(
    lambda x: x.find_element_by_id("someId"))
16
Carl

Comme mentionné dans le réponse de David Cullen , j'ai toujours recommandé d'utiliser une ligne comme celle-ci:

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

Il m’était difficile pour moi de trouver n’importe où tous les localisateurs possibles pouvant être utilisés avec la syntaxe By; j’ai donc pensé qu’il serait utile de fournir ici la liste. Selon Web Scraping with Python de Ryan Mitchell:

ID

Utilisé dans l'exemple; trouve des éléments par leur attribut HTML id

CLASS_NAME

Utilisé pour rechercher des éléments par leur attribut de classe HTML. Pourquoi cette fonction _CLASS_NAME_ n'est-elle pas simplement CLASS? L'utilisation du formulaire _object.CLASS_ créerait des problèmes pour la bibliothèque Java de Selenium, où _.class_ est une méthode réservée. Afin de conserver la syntaxe Selenium cohérente entre différentes langues, nous avons utilisé _CLASS_NAME_.

CSS_SELECTOR

Recherchez des éléments par leur classe, leur identifiant ou leur nom de balise, à l'aide de la convention _#idName_, _.className_, tagName.

LINK_TEXT

Trouve les balises HTML d'après le texte qu'elles contiennent. Par exemple, un lien indiquant "Suivant" peut être sélectionné à l'aide de _(By.LINK_TEXT, "Next")_.

PARTIAL_LINK_TEXT

Similaire à _LINK_TEXT_, mais correspond à une chaîne partielle.

NAME

Trouve les balises HTML par leur attribut name. C'est pratique pour les formulaires HTML.

TAG_NAME

Affine les balises HTML par leur nom.

XPATH

Utilise une expression XPath ... pour sélectionner les éléments correspondants.

15
J0ANMM

Sur une note de côté, au lieu de faire défiler 100 fois vers le bas, vous pouvez vérifier s'il n'y a plus de modifications dans le DOM (nous sommes dans le cas du bas de la page étant AJAX chargé paresseux)

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
7
raffamaiden

Pourquoi ne pas mettre WebDriverWait dans While en boucle et intercepter les exceptions.

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"
3
Rao

Ici je l'ai fait en utilisant un formulaire plutôt 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
3
ahmed abdelmalek

Avez-vous essayé driver.implicitly_wait. C'est comme un paramètre pour le pilote. Vous ne l'appelez donc qu'une fois dans la session. Il indique au pilote d'attendre le temps imparti jusqu'à ce que chaque commande puisse être exécutée.

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

Par conséquent, si vous définissez un délai d’attente de 10 secondes, la commande sera exécutée dès que possible et attendra 10 secondes avant d’abandonner. J'ai utilisé cela dans des scénarios de défilement similaires, je ne vois donc pas pourquoi cela ne fonctionnerait pas dans votre cas. J'espère que c'est utile.

2
seeiespi