Attendez que page soit chargée avec Selenium WebDriver pour Python
je veux gratter toutes les données d'une page implémentée par un scroll 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 vers le bas, j'ai besoin d'attendre 5 secondes, ce qui est généralement suffisant pour que la page termine 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 en 5 secondes. Comment puis-je détecter si la page a fini de charger les nouveaux contenus chaque fois que je scroll vers le bas? Si je peux détecter cela, je peux faire défiler vers le bas à nouveau pour voir plus de contenus une fois que je sais que la page a terminé le chargement. C'est plus efficace.
8 réponses
le webdriver
attendra qu'une page se charge par défaut via la méthode .get()
.
comme vous pourriez être à la recherche d'un élément spécifique comme @user227215 l'a dit, vous devriez 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:
I devrait mentionner que le webdriver
attendra qu'une page se charge 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 jusqu'à ce que la page soit complètement chargée, puis passer à la commande suivante dans le code. Mais lorsque vous postez une demande ajax, webdriver
n'attend pas et il est de votre responsabilité d'attendre un temps approprié pour que la page ou une partie de la page se charge; il y a donc un module nommé expected_conditions
.
en essayant de passer find_element_by_id
au constructeur pour presence_of_element_located
(comme montré dans la réponse acceptée ) causé NoSuchElementException
pour être augmenté. J'ai dû utiliser la syntaxe dans fragles ' commentaire :
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"
ceci correspond à l'exemple dans la documentation . Voici un lien vers la documentation pour par .
trouver ci-dessous 3 méthodes:
readyState
page de contrôle readyState (pas 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'aide
wait_for
est bonne, mais malheureusementclick_through_to_new_page
est ouvert à la condition de course où nous parvenons à exécuter le script dans l'ancienne page, avant que le navigateur a commencé à traiter le clic, etpage_has_loaded
retourne juste vrai tout de suite.
id
Comparant nouvelle page de codes 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 des ids ne soit pas aussi efficace que d'attendre des exceptions de référence périmées.
staleness_of
utilisant staleness_of
méthode:
@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 Harry's blog .
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"))
comme mentionné dans la réponse de David Cullen , j'ai vu toujours recommandé d'utiliser une ligne comme la suivante:
element_present = EC.presence_of_element_located((By.ID, 'element_id'))
WebDriverWait(driver, timeout).until(element_present)
il m'a été difficile de trouver n'importe où tous les locateurs possibles qui peuvent être utilisés avec la syntaxe By
, donc j'ai pensé qu'il serait utile de fournir ici la liste.
Selon grattage Web avec Python par Ryan Mitchell:
ID
utilisé dans l'exemple; trouve des éléments par leur attribut HTML id
CLASS_NAME
utilisé pour trouver des éléments par leur attribut de classe HTML. Pourquoi est-ce fonction
CLASS_NAME
pas simplementCLASS
? En utilisant le formulaireobject.CLASS
serait de créer problèmes pour la bibliothèque Java de Selenium, où.class
est réservé méthode. Afin de garder la syntaxe du sélénium cohérente entre différentes langues,CLASS_NAME
a été utilisé à la place.
CSS_SELECTOR
trouver les éléments par leur classe, id, ou nom d'étiquette, en utilisant le
#idName
,.className
,tagName
de la convention.
LINK_TEXT
trouve les balises HTML par le texte qu'elles contiennent. Par exemple, un lien qui dit " suivant "peut être sélectionné en utilisant
(By.LINK_TEXT, "Next")
.
PARTIAL_LINK_TEXT
Semblables
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
trouve les balises HTML par leur nom de balise.
XPATH
utilise une expression XPath ... pour sélectionner les éléments correspondants.
sur une note latérale, au lieu de défiler 100 fois vers le bas, vous pouvez vérifier s'il n'y a plus de modifications au DOM (nous sommes dans le cas du bas de la page étant AJAX paresseux-chargé)
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
Que Diriez-vous de mettre WebDriverWait tout en boucle et d'attraper 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"
avez-vous essayé driver.implicitly_wait
. C'est comme un paramètre pour le pilote, donc vous ne l'appelez qu'une fois dans la session et il indique au pilote d'attendre le temps donné jusqu'à ce que chaque commande puisse être exécutée.
driver = webdriver.Chrome()
driver.implicitlyWait(10)
Donc, si vous définissez un temps d'attente de 10 secondes, il exécutera la commande dès que possible, attendre 10 secondes avant d'abandonner. J'ai utilisé ça dans des scénarios similaires, donc je ne vois pas pourquoi ça ne marcherait pas dans votre cas. Espérer ceci est utile :)