Warten Sie, bis die Seite mit Selenium WebDriver für Python geladen wurde

180

Ich möchte alle Daten einer Seite kratzen, die durch eine unendliche Schriftrolle implementiert wurde. Der folgende Python-Code funktioniert.

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

Dies bedeutet, dass ich jedes Mal, wenn ich nach unten scrolle, 5 Sekunden warten muss, was im Allgemeinen ausreicht, damit die Seite den neu generierten Inhalt vollständig lädt. Dies ist jedoch möglicherweise nicht zeiteffizient. Die Seite lädt den neuen Inhalt möglicherweise innerhalb von 5 Sekunden. Wie kann ich feststellen, ob die Seite den neuen Inhalt bei jedem Bildlauf nach unten geladen hat? Wenn ich dies feststellen kann, kann ich erneut nach unten scrollen, um weitere Inhalte anzuzeigen, sobald ich weiß, dass die Seite vollständig geladen wurde. Dies ist zeiteffizienter.

Apogne
quelle
1
Es kann hilfreich sein, etwas mehr über die Seite zu erfahren. Sind die Elemente sequentiell oder vorhersehbar? Sie können auf das Laden von Elementen warten, indem Sie die Sichtbarkeit mit id oder xpath überprüfen
user2272115
Ich
crawle
1
mögliches Duplikat von Seitenladung oder
Zeitüberschreitung
Beantwortet das deine Frage? Warten Sie auf das Laden der Seite in Selenium
Matej J vor

Antworten:

233

Das webdriverwartet darauf, dass eine Seite standardmäßig über die .get()Methode geladen wird .

Da Sie möglicherweise nach einem bestimmten Element suchen, wie @ user227215 sagte, sollten Sie verwenden WebDriverWait, um auf ein Element auf Ihrer Seite zu warten:

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

Ich habe es zum Überprüfen von Warnungen verwendet. Sie können beliebige andere Typmethoden verwenden, um den Locator zu finden.

EDIT 1:

Ich sollte erwähnen, dass das webdriverstandardmäßig auf das Laden einer Seite wartet. Es wartet nicht auf das Laden innerhalb von Frames oder auf Ajax-Anfragen. Wenn Sie verwenden .get('url'), wartet Ihr Browser, bis die Seite vollständig geladen ist, und fährt dann mit dem nächsten Befehl im Code fort. Wenn Sie jedoch eine Ajax-Anfrage stellen, webdriverwarten Sie nicht und es liegt in Ihrer Verantwortung, eine angemessene Zeit zu warten, bis die Seite oder ein Teil der Seite geladen ist. Es gibt also ein Modul mit dem Namen expected_conditions.

Zeinab Abbasimazar
quelle
3
Ich habe das Argument "find_element () erhalten, nachdem * eine Sequenz sein muss, nicht WebElement", geändert in "WebDriverWait (Browser, Verzögerung). Bis (EC.presence_of_element_located ((By.ID," IdOfMyElement ")))" siehe manuelles Selen- python.readthedocs.org/en/latest/waits.html
fragles
2
Der Kommentar von @fragles und die Antwort von David Cullen haben bei mir funktioniert. Vielleicht könnte diese akzeptierte Antwort entsprechend aktualisiert werden?
Michael Ohlrogge
6
Durch browser.find_element_by_id('IdOfMyElement')das Passieren wird ein NoSuchElementExceptionerhöht. In der Dokumentation heißt es, ein Tupel zu übergeben, das so aussieht : (By.ID, 'IdOfMyElement'). Siehe meine Antwort
David Cullen
2
Hoffentlich hilft dies jemand anderem, weil mir anfangs nicht klar war: WebDriverWait gibt tatsächlich ein Webobjekt zurück, an dem Sie dann eine Aktion ausführen (z. B. click()), Text auslesen usw. Ich hatte den falschen Eindruck, dass es einfach ist verursachte eine Wartezeit, nach der man das Element noch finden musste. Wenn Sie warten und anschließend ein Element suchen, tritt ein Fehler bei Selen auf, da versucht wird, das Element zu finden, während das alte Warten noch verarbeitet wird (hoffentlich ist dies sinnvoll). Unter dem Strich müssen Sie das Element nach der Verwendung von WebDriverWait nicht finden - es ist bereits ein Objekt.
Ben Wilson
1
@ Gopgop Wow das ist so hässlich ist kein konstruktiver Kommentar. Was ist daran hässlich? Wie könnte es besser gemacht werden?
Modus Tollens
72

Der Versuch, find_element_by_idan den Konstruktor zu übergeben presence_of_element_located(wie in der akzeptierten Antwort gezeigt ), wurde ausgelöst NoSuchElementException. Ich musste die Syntax in Fragles ' Kommentar verwenden :

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"

Dies entspricht dem Beispiel in der Dokumentation . Hier ist ein Link zur Dokumentation für By .

David Cullen
quelle
2
Danke dir! Ja, das wurde auch für mich gebraucht. ID ist nicht das einzige Attribut, das verwendet werden kann. Um eine vollständige Liste zu erhalten, verwenden Sie help (By). ZB habe ich benutztEC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge
So funktioniert das auch bei mir! Ich habe eine zusätzliche Antwort geschrieben , die die verschiedenen Locators erweitert, die für das ByObjekt verfügbar sind .
J0ANMM
Ich habe eine Folgefrage gestellt, die sich mit Erwartungen befasst, bei denen verschiedene Seiten geladen werden können und nicht immer dieselbe Seite: stackoverflow.com/questions/51641546/…
Liquidgenius
48

Finden Sie unten 3 Methoden:

readyState

Überprüfung der Seite readyState (nicht zuverlässig):

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'

Die wait_forHilfsfunktion ist gut, aber leider click_through_to_new_pageoffen für die Race-Bedingung, bei der es uns gelingt, das Skript auf der alten Seite auszuführen, bevor der Browser mit der Verarbeitung des Klicks begonnen hat, und page_has_loadedsofort true zurückgibt.

id

Vergleich neuer Seiten-IDs mit den alten:

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 ist möglich, dass das Vergleichen von IDs nicht so effektiv ist wie das Warten auf veraltete Referenzausnahmen.

staleness_of

Mit staleness_ofMethode:

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

Weitere Informationen finden Sie in Harrys Blog .

Kenorb
quelle
Warum sagst du das self.driver.execute_script('return document.readyState;')nicht zuverlässig? Es scheint perfekt für meinen Anwendungsfall zu funktionieren, der darauf wartet, dass eine statische Datei in einen neuen Tab geladen wird (der über Javascript in einem anderen Tab anstelle von .get () geöffnet wird).
Arthur Hebert
1
@ArthurHebert Konnte aufgrund der Rennbedingungen nicht zuverlässig sein, ich habe relevante Zitate hinzugefügt.
Kenorb
23

Wie in der Antwort von David Cullen erwähnt , habe ich immer Empfehlungen gesehen, eine Zeile wie die folgende zu verwenden:

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

Es war schwierig für mich, irgendwo alle möglichen Locators zu finden, die mit dem verwendet werden können By, daher dachte ich, es wäre nützlich, die Liste hier bereitzustellen. Laut Web Scraping with Python von Ryan Mitchell:

ID

Im Beispiel verwendet; findet Elemente anhand ihres HTML-ID-Attributs

CLASS_NAME

Wird verwendet, um Elemente anhand ihres HTML-Klassenattributs zu finden. Warum ist diese Funktion CLASS_NAMEnicht einfach CLASS? Die Verwendung des Formulars object.CLASS würde Probleme für die Java-Bibliothek von Selenium verursachen, bei der .classes sich um eine reservierte Methode handelt. Um die Selenium-Syntax zwischen verschiedenen Sprachen konsistent zu halten, CLASS_NAMEwurde stattdessen verwendet.

CSS_SELECTOR

Findet Elemente durch ihre Klasse, ID oder Tag - Namen, mit dem #idName, .className, tagNameKonvention.

LINK_TEXT

Findet HTML-Tags anhand des darin enthaltenen Textes. Beispielsweise kann ein Link mit der Aufschrift "Weiter" mit ausgewählt werden (By.LINK_TEXT, "Next").

PARTIAL_LINK_TEXT

Ähnlich wie LINK_TEXT, stimmt jedoch mit einer Teilzeichenfolge überein.

NAME

Findet HTML-Tags anhand ihres Namensattributs. Dies ist praktisch für HTML-Formulare.

TAG_NAME

Findet HTML-Tags anhand ihres Tag-Namens.

XPATH

Verwendet einen XPath-Ausdruck ..., um übereinstimmende Elemente auszuwählen.

J0ANMM
quelle
5
In der Dokumentation zu By sind die Attribute aufgeführt, die als Locators verwendet werden können.
David Cullen
1
Das hatte ich gesucht! Vielen Dank! Nun, jetzt sollte es einfacher zu finden sein, da Google mich zu dieser Frage geschickt hat, aber nicht zur offiziellen Dokumentation.
J0ANMM
Vielen Dank für das Zitat aus dem Buch. Es ist viel klarer als die Dokumentation.
ZygD
11

Nebenbei bemerkt, anstatt 100 Mal nach unten zu scrollen, können Sie überprüfen, ob keine Änderungen mehr am DOM vorgenommen wurden (wir sind im Fall, dass das Ende der Seite AJAX faul geladen wird).

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
quelle
Das ist nützlich. Was repräsentiert der 500er? Ist es groß genug, um zum Ende der Seite zu gelangen?
Moondra
Es ist der Betrag, um den die Seite scrollen soll ... Sie sollten ihn so hoch wie möglich einstellen. Ich habe gerade herausgefunden, dass diese Nummer für mich ausreicht, da dadurch die Seite bis zum Ende
gescrollt wird,
Dies ist hilfreich, wenn Sie sicherstellen möchten, dass alle Kommentare zu einem Problem in gitlab vollständig geladen sind.
bgStack15
7

Hast du es versucht driver.implicitly_wait? Es ist wie eine Einstellung für den Treiber, Sie rufen sie also nur einmal in der Sitzung auf und der Treiber wird grundsätzlich angewiesen, die angegebene Zeit zu warten, bis jeder Befehl ausgeführt werden kann.

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

Wenn Sie also eine Wartezeit von 10 Sekunden festlegen, wird der Befehl so schnell wie möglich ausgeführt und 10 Sekunden gewartet, bevor er aufgibt. Ich habe dies in ähnlichen Scroll-Down-Szenarien verwendet, daher verstehe ich nicht, warum es in Ihrem Fall nicht funktionieren würde. Hoffe das ist hilfreich.

Um diese Antwort korrigieren zu können, muss ich neuen Text hinzufügen. Stellen Sie sicher, dass Sie ein Kleinbuchstaben "w" verwenden implicitly_wait.

seeiespi
quelle
Was ist der Unterschied zwischen implizitem Warten und Webdriverwait?
song0089
4

Wie wäre es, wenn Sie WebDriverWait in die While-Schleife einfügen und die Ausnahmen abfangen.

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
quelle
Du brauchst die Schleife nicht?
Corey Goldberg
4

Hier habe ich es mit einer ziemlich einfachen Form gemacht:

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
quelle
1

Mit dieser Funktion können Sie das ganz einfach tun:

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

und wenn Sie nach dem vollständigen Laden der Seite etwas tun möchten, können Sie Folgendes verwenden:

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
quelle