Tunggu hingga halaman dimuat dengan Selenium WebDriver untuk Python


181

Saya ingin mengikis semua data halaman yang diimplementasikan oleh gulir yang tak terbatas. Kode python berikut berfungsi.

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

Ini berarti setiap kali saya gulir ke bawah, saya harus menunggu 5 detik, yang umumnya cukup bagi halaman untuk menyelesaikan pemuatan konten yang baru dibuat. Tapi, ini mungkin tidak efisien waktu. Halaman mungkin selesai memuat konten baru dalam 5 detik. Bagaimana saya bisa mendeteksi apakah halaman selesai memuat konten baru setiap kali saya gulir ke bawah? Jika saya dapat mendeteksi ini, saya dapat menggulir ke bawah lagi untuk melihat lebih banyak konten setelah saya tahu halaman selesai memuat. Ini lebih efisien waktu.


1
Mungkin membantu untuk mengetahui lebih banyak tentang halaman tersebut. Apakah elemen berurutan atau dapat diprediksi? Anda bisa menunggu elemen dimuat dengan memeriksa visiblity menggunakan id atau xpath
user2272115

Saya merangkak halaman berikut: pinterest.com/cremedelacrumb/yum
apogne


Apakah ini menjawab pertanyaan Anda? Tunggu pemuatan halaman di Selenium
Matej J

Jawaban:


234

The webdriverakan menunggu halaman untuk beban secara default melalui .get()metode.

Karena Anda mungkin mencari beberapa elemen spesifik seperti yang dikatakan @ user227215, Anda harus menggunakan WebDriverWaituntuk menunggu elemen yang terletak di halaman Anda:

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

Saya telah menggunakannya untuk memeriksa peringatan. Anda dapat menggunakan metode tipe apa pun lainnya untuk menemukan locator.

EDIT 1:

Saya harus menyebutkan bahwa webdriverakan menunggu halaman dimuat secara default. Itu tidak menunggu memuat frame di dalam atau untuk permintaan ajax. Itu berarti ketika Anda menggunakan .get('url'), browser Anda akan menunggu sampai halaman dimuat sepenuhnya dan kemudian pergi ke perintah selanjutnya dalam kode. Tetapi ketika Anda memposting permintaan ajax, webdriverjangan menunggu dan itu adalah tanggung jawab Anda untuk menunggu waktu yang sesuai untuk halaman atau bagian dari halaman untuk memuat; jadi ada modul bernama expected_conditions.


3
Saya mendapatkan "find_element () argumen setelah * harus menjadi urutan, bukan WebElement" diubah menjadi "WebDriverWait (browser, delay) .until (EC.presence_of_element_located ((By.ID," IdOfMyElement ")))" lihat manual selenium- python.readthedocs.org/en/latest/waits.html
fragles

2
Komentar oleh @fragles dan jawaban oleh David Cullen adalah yang berhasil bagi saya. Mungkin jawaban yang diterima ini dapat diperbarui?
Michael Ohlrogge

6
Passing browser.find_element_by_id('IdOfMyElement')menyebabkan a NoSuchElementExceptiondinaikkan. The dokumentasi mengatakan untuk lulus tuple yang terlihat seperti ini: (By.ID, 'IdOfMyElement'). Lihat jawaban saya
David Cullen

2
Mudah-mudahan ini membantu orang lain keluar karena awalnya tidak jelas bagi saya: WebDriverWait sebenarnya akan mengembalikan objek web yang kemudian dapat Anda lakukan tindakan pada (misalnya click()), membaca teks dari dll. Saya berada di bawah kesan yang salah bahwa itu hanya menyebabkan menunggu, setelah itu kamu masih harus menemukan elemen. Jika Anda menunggu, kemudian menemukan elemen setelahnya, selenium akan error karena mencoba mencari elemen sementara menunggu lama masih diproses (mudah-mudahan itu masuk akal). Intinya adalah, Anda tidak perlu menemukan elemen setelah menggunakan WebDriverWait - itu sudah menjadi objek.
Ben Wilson

1
@ Gopgop Wow ini sangat jelek bukan komentar yang membangun. Apa yang buruk tentang itu? Bagaimana itu bisa dibuat lebih baik?
Modus Tollens

73

Mencoba meneruskan find_element_by_idke konstruktor untuk presence_of_element_located(seperti yang ditunjukkan dalam jawaban yang diterima ) menyebabkan NoSuchElementExceptiondimunculkan. Saya harus menggunakan sintaks dalam komentar fragles ' :

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"

Ini cocok dengan contoh dalam dokumentasi . Berikut ini tautan ke dokumentasi untuk oleh .


2
Terima kasih! ya, ini diperlukan untuk saya juga. ID bukan satu-satunya atribut yang dapat digunakan, untuk mendapatkan daftar lengkap, gunakan bantuan (oleh). Misalnya saya menggunakanEC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge

Itulah cara kerjanya bagi saya juga! Saya menulis jawaban tambahan yang meluas pada pencari yang berbeda yang tersedia dengan Byobjek.
J0ANMM

Saya telah memposting pertanyaan lanjutan yang berhubungan dengan harapan di mana halaman yang berbeda dapat dimuat, dan tidak selalu halaman yang sama: stackoverflow.com/questions/51641546/…
Liquidgenius

48

Temukan 3 metode di bawah ini:

readyState

Memeriksa halaman readyState (tidak dapat diandalkan):

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'

Fungsi wait_forhelper baik, tetapi sayangnya click_through_to_new_pageterbuka untuk kondisi balapan tempat kami mengelola untuk mengeksekusi skrip di halaman lama, sebelum browser mulai memproses klik, dan page_has_loadedlangsung mengembalikan true.

id

Membandingkan id halaman baru dengan yang lama:

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

Mungkin saja membandingkan id tidak seefektif menunggu pengecualian referensi basi.

staleness_of

Menggunakan staleness_ofmetode:

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

Untuk lebih jelasnya, periksa blog Harry .


Mengapa Anda mengatakan itu self.driver.execute_script('return document.readyState;')tidak dapat diandalkan? Tampaknya berfungsi dengan baik untuk kasus penggunaan saya, yang sedang menunggu file statis untuk dimuat di tab baru (yang dibuka melalui javascript di tab lain, bukan .get ()).
Arthur Hebert

1
@ArthurHebert Mungkin tidak dapat diandalkan karena kondisi balapan, saya telah menambahkan kutipan yang relevan.
kenorb

23

Seperti yang disebutkan dalam jawaban dari David Cullen , saya selalu melihat rekomendasi untuk menggunakan baris seperti berikut:

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

Sulit bagi saya untuk menemukan di suatu tempat semua kemungkinan pelacak yang dapat digunakan dengan By, jadi saya pikir akan bermanfaat untuk memberikan daftar di sini. Menurut Web Scraping with Python oleh Ryan Mitchell:

ID

Digunakan dalam contoh; menemukan elemen dengan atribut id HTML mereka

CLASS_NAME

Digunakan untuk menemukan elemen berdasarkan atribut kelas HTML mereka. Mengapa fungsi ini CLASS_NAMEtidak sederhana CLASS? Menggunakan formulir object.CLASS akan membuat masalah untuk perpustakaan Java Selenium, di mana .classmetode yang disediakan. Untuk menjaga agar sintaks Selenium konsisten antara berbagai bahasa, CLASS_NAMEdigunakan sebagai gantinya.

CSS_SELECTOR

Menemukan unsur-unsur berdasarkan kelas, id, atau nama tag mereka, menggunakan #idName, .className, tagNamekonvensi.

LINK_TEXT

Menemukan tag HTML berdasarkan teks yang dikandungnya. Misalnya, tautan yang mengatakan "Berikutnya" dapat dipilih menggunakan (By.LINK_TEXT, "Next").

PARTIAL_LINK_TEXT

Mirip dengan LINK_TEXT, tetapi cocok pada string parsial.

NAME

Menemukan tag HTML berdasarkan atribut namanya. Ini berguna untuk formulir HTML.

TAG_NAME

Menemukan tag HTML dengan nama tag mereka.

XPATH

Menggunakan ekspresi XPath ... untuk memilih elemen yang cocok.


5
The dokumentasi untuk By berisi daftar atribut yang dapat digunakan sebagai pencari.
David Cullen

1
Itulah yang saya cari! Terima kasih! Nah, sekarang seharusnya lebih mudah ditemukan karena google mengirim saya ke pertanyaan ini, tetapi tidak ke dokumentasi resmi.
J0ANMM

Terima kasih atas kutipan dari buku ini. Ini jauh lebih jelas daripada dokumentasi.
ZygD


11

Di samping catatan, alih-alih menggulir ke bawah 100 kali, Anda dapat memeriksa apakah tidak ada lagi modifikasi pada DOM (kami dalam kasus bagian bawah halaman menjadi malas-malas 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

Ini bermanfaat. Namun apa yang diwakili oleh 500? Apakah cukup besar untuk sampai ke akhir halaman?
Moondra

Ini adalah jumlah yang harus digulir halaman ... Anda harus mengaturnya setinggi mungkin. Saya baru tahu bahwa angka ini cukup bagi saya, karena itu membuat halaman gulir sampai ke bawah sampai unsur-unsur AJAX bermalas-malas, memacu kebutuhan untuk memuat ulang halaman lagi
raffaem

Ini membantu ketika mencoba untuk memastikan semua komentar tentang masalah di gitlab dimuat penuh.
bgStack15

7

Sudahkah Anda mencoba driver.implicitly_wait. Ini seperti pengaturan untuk driver, jadi Anda hanya memanggilnya sekali dalam sesi dan pada dasarnya memberitahu pengemudi untuk menunggu jumlah waktu yang diberikan sampai setiap perintah dapat dieksekusi.

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

Jadi, jika Anda menetapkan waktu tunggu 10 detik, perintah itu akan dieksekusi sesegera mungkin, tunggu 10 detik sebelum menyerah. Saya telah menggunakan ini dalam skenario gulir-turun yang sama, jadi saya tidak melihat mengapa itu tidak berfungsi dalam kasus Anda. Semoga ini bisa membantu.

Untuk dapat memperbaiki jawaban ini, saya harus menambahkan teks baru. Pastikan untuk menggunakan huruf kecil 'w' dalam implicitly_wait.


Apa perbedaan antara menunggu secara implisit dan menunggu web?
song0089

4

Bagaimana menempatkan WebDriverWait di loop Sementara dan menangkap pengecualian.

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"

Anda tidak membutuhkan loop?
Corey Goldberg

4

Di sini saya melakukannya menggunakan formulir yang agak sederhana:

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

1

Anda dapat melakukannya dengan sangat sederhana dengan fungsi ini:

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

dan ketika Anda ingin melakukan sesuatu setelah pemuatan halaman selesai, Anda dapat menggunakan:

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')")
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.