Poczekaj, aż strona zostanie załadowana przy użyciu Selenium WebDriver for Python

181

Chcę zeskrobać wszystkie dane strony zaimplementowanej przez nieskończone przewijanie. Działa następujący kod Pythona.

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

Oznacza to, że za każdym razem, gdy przewijam w dół, muszę czekać 5 sekund, co generalnie wystarcza, aby strona zakończyła ładowanie nowo wygenerowanej zawartości. Ale to może nie być efektywne czasowo. Strona może zakończyć ładowanie nowej zawartości w ciągu 5 sekund. Jak mogę sprawdzić, czy strona zakończyła ładowanie nowej zawartości za każdym razem, gdy przewijam w dół? Jeśli uda mi się to wykryć, mogę ponownie przewinąć w dół, aby zobaczyć więcej treści po zakończeniu ładowania strony. Jest to bardziej efektywne czasowo.

apogeum
źródło
1
Warto dowiedzieć się więcej o tej stronie. Czy elementy są sekwencyjne czy przewidywalne? Możesz poczekać na załadowanie elementów, sprawdzając widoczność za pomocą id lub xpath
user2272115
Indeksuję
1
możliwy duplikat Niezawodnie wykryj ładowanie strony lub
przekroczenie limitu
Czy to odpowiada na twoje pytanie? Poczekaj na załadowanie strony w Selenium
Matej J

Odpowiedzi:

234

webdriverBędzie czekać na załadowanie strony domyślnie poprzez .get()metody.

Ponieważ możesz szukać konkretnego elementu, jak powiedział @ user227215, powinieneś WebDriverWaitpoczekać na element znajdujący się na twojej stronie:

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

Użyłem go do sprawdzania alertów. Możesz użyć innych metod typu, aby znaleźć lokalizator.

EDYCJA 1:

Powinienem wspomnieć, że webdriverdomyślnie będzie czekać na załadowanie strony. Nie czeka na załadowanie wewnątrz ramek ani na żądania AJAX. Oznacza to, że kiedy używasz .get('url'), Twoja przeglądarka będzie czekać, aż strona zostanie całkowicie załadowana, a następnie przejdzie do następnego polecenia w kodzie. Ale kiedy wysyłasz żądanie Ajax, webdrivernie czekaj i Twoim obowiązkiem jest odczekanie odpowiedniej ilości czasu na załadowanie strony lub jej części; więc istnieje moduł o nazwie expected_conditions.

Zeinab Abbasimazar
źródło
3
I był już "find_element () Argument po * muszą być sekwencją, nie WebElement" zmieniono na "WebDriverWait (przeglądarka, opóźnienie) ważnych elementów.Dopóki (EC.presence_of_element_located ((By.ID, "IdOfMyElement")))", patrz instrukcja selenium- python.readthedocs.org/en/latest/waits.html
fragles
2
Komentarz @fragles i odpowiedź Davida Cullena pomogły mi. Może ta zaakceptowana odpowiedź mogłaby zostać odpowiednio zaktualizowana?
Michael Ohlrogge
6
Podanie browser.find_element_by_id('IdOfMyElement')powoduje NoSuchElementExceptionpodniesienie a. Dokumentacja mówi przekazać krotki, który wygląda tak: (By.ID, 'IdOfMyElement'). Zobacz moją odpowiedź
David Cullen
2
Mam nadzieję, że pomoże to komuś innemu, ponieważ początkowo nie było dla mnie jasne: WebDriverWait zwróci obiekt sieciowy, na którym można następnie wykonać akcję (np. click()), Odczytać tekst itp. Miałem błędne wrażenie, że spowodowało oczekiwanie, po którym wciąż trzeba było znaleźć element. Jeśli zaczekasz, a później element find, selen zostanie błędnie znaleziony, ponieważ próbuje znaleźć element, podczas gdy stare oczekiwanie nadal jest przetwarzane (mam nadzieję, że ma to sens). Podsumowując, nie musisz znajdować elementu po użyciu WebDriverWait - jest to już obiekt.
Ben Wilson
1
@Gopgop Wow, to jest tak brzydkie, nie jest konstruktywnym komentarzem. Co w tym brzydkiego? Jak można to ulepszyć?
Modus Tollens
72

Próba przekazania find_element_by_iddo konstruktora for presence_of_element_located(jak pokazano w zaakceptowanej odpowiedzi ) spowodowana NoSuchElementExceptionzostała podniesiona. Musiałem użyć składni w Fragles ' komentarzu :

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"

Jest to zgodne z przykładem w dokumentacji . Oto link do dokumentacji By .

David Cullen
źródło
2
Dziękuję Ci! tak, mi to też było potrzebne. ID nie jest jedynym atrybutem, którego można użyć, aby uzyskać pełną listę, użyj help (By). Np. UżyłemEC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge
U mnie tak to działa! Napisałem dodatkową odpowiedź, rozwijając różne lokalizatory, które są dostępne w Byobiekcie.
J0ANMM
Wysłałem kolejne pytanie dotyczące oczekiwań, gdzie mogą być ładowane różne strony, a nie zawsze ta sama strona: stackoverflow.com/questions/51641546/ ...
Liquidgenius
48

Znajdź poniżej 3 metody:

readyState

Sprawdzanie strony readyState (niewiarygodne):

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'

Funkcja wait_forpomocnika jest dobra, ale niestety click_through_to_new_pagejest otwarta na stan wyścigu, w którym udaje nam się wykonać skrypt na starej stronie, zanim przeglądarka zacznie przetwarzać kliknięcie i page_has_loadedpo prostu zwraca prawdę od razu.

id

Porównanie nowych identyfikatorów stron ze starymi:

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

Możliwe, że porównywanie identyfikatorów nie jest tak skuteczne, jak oczekiwanie na nieaktualne wyjątki odwołań.

staleness_of

Za pomocą staleness_ofmetody:

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

Więcej szczegółów znajdziesz na blogu Harry'ego .

kenorb
źródło
Dlaczego to mówisz self.driver.execute_script('return document.readyState;') nie jest wiarygodne? Wygląda na to, że działa idealnie w moim przypadku użycia, który czeka na załadowanie pliku statycznego w nowej karcie (która jest otwierana za pomocą JavaScript w innej karcie zamiast .get ()).
Arthur Hebert
1
@ArthurHebert Może nie być wiarygodne ze względu na stan wyścigu, dodałem odpowiednie cytowanie.
kenorb
23

Jak wspomniano w odpowiedzi Davida Cullena , zawsze widziałem zalecenia, aby użyć linii takiej jak poniższa:

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

Trudno mi było znaleźć gdzieś wszystkie możliwe lokalizatory, których można użyć z By , więc pomyślałem, że warto podać listę tutaj. Według Web Scraping with Python autorstwa Ryana Mitchella:

ID

Użyty w przykładzie; wyszukuje elementy według ich atrybutu identyfikatora HTML

CLASS_NAME

Używane do znajdowania elementów według ich atrybutu klasy HTML. Dlaczego ta funkcja CLASS_NAMEnie jest prostaCLASS ? Użycie formularza object.CLASS spowodowałoby problemy dla biblioteki Java Selenium, gdzie .classjest zastrzeżoną metodą. Aby zachować spójność składni Selenium między różnymi językami, CLASS_NAMEużyto zamiast tego.

CSS_SELECTOR

Wyszukuje elementy według ich klasy, identyfikatora lub nazwy tagu przy użyciu #idName , .className, tagNamekonwencję.

LINK_TEXT

Znajduje znaczniki HTML według zawartego w nich tekstu. Na przykład łącze z napisem „Dalej” można wybrać za pomocą (By.LINK_TEXT, "Next").

PARTIAL_LINK_TEXT

Podobny do LINK_TEXT , ale pasuje do częściowego ciągu.

NAME

Znajduje tagi HTML według atrybutu nazwy. Jest to przydatne w przypadku formularzy HTML.

TAG_NAME

Znajduje tagi HTML według ich nazwy.

XPATH

Używa wyrażenia XPath ... do wybierania pasujących elementów.

J0ANMM
źródło
5
Dokumentacji przez listę atrybutów, które mogą być używane jako lokalizatory.
David Cullen
1
To było to, czego szukałem! Dzięki! Cóż, teraz powinno być łatwiej znaleźć, ponieważ Google wysyłało mnie na to pytanie, ale nie do oficjalnej dokumentacji.
J0ANMM
Dzięki za cytat z książki. Jest znacznie bardziej przejrzysty niż dokumentacja.
ZygD
11

Na marginesie, zamiast przewijać w dół 100 razy, możesz sprawdzić, czy nie ma już żadnych modyfikacji w DOM (mamy w przypadku, gdy dół strony jest leniwie ładowany 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
źródło
To jest przydatne. Co jednak reprezentuje 500? Czy jest wystarczająco duży, aby dotrzeć do końca strony?
Moondra
To ilość, o jaką powinna przewijać się strona ... należy ustawić ją jak najwyżej. Właśnie się dowiedziałem, że ta liczba mi wystarczy, ponieważ sprawia, że ​​strona przewija się do dołu, aż elementy AJAX są leniwie ładowane, co powoduje konieczność ponownego załadowania strony
raffaem
Pomaga to, gdy próbujesz upewnić się, że wszystkie komentarze dotyczące problemu w gitlab są w pełni załadowane.
bgStack15
7

Próbowałeś driver.implicitly_wait. Jest to podobne do ustawienia dla sterownika, więc wywołujesz je tylko raz w sesji i zasadniczo mówi kierowcy, aby czekał przez określony czas, aż każde polecenie zostanie wykonane.

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

Więc jeśli ustawisz czas oczekiwania na 10 sekund, wykona polecenie tak szybko, jak to możliwe, czekając 10 sekund, zanim się podda. Używałem tego w podobnych scenariuszach przewijania w dół, więc nie rozumiem, dlaczego nie zadziałaby w twoim przypadku. Mam nadzieję, że to jest pomocne.

Aby móc poprawić tę odpowiedź, muszę dodać nowy tekst. Pamiętaj, aby użyć małej litery „w” w implicitly_wait.

seeiespi
źródło
Jaka jest różnica między implicitly wait i webdriverwait?
song0089
4

Co powiesz na umieszczenie WebDriverWait w pętli While i przechwytywanie wyjątków.

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
źródło
nie potrzebujesz pętli?
Corey Goldberg
4

Tutaj zrobiłem to za pomocą dość prostej formy:

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
źródło
1

Możesz to zrobić bardzo prosto za pomocą tej funkcji:

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

a jeśli chcesz coś zrobić po zakończeniu ładowania strony, możesz użyć:

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
źródło