รอจนกว่าหน้าโหลดด้วย Selenium WebDriver สำหรับ Python


181

ฉันต้องการขูดข้อมูลทั้งหมดของหน้าเว็บที่นำมาใช้โดยการเลื่อนแบบไม่มีที่สิ้นสุด รหัสหลามต่อไปนี้ใช้งานได้

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

ซึ่งหมายความว่าทุกครั้งที่ฉันเลื่อนลงไปด้านล่างฉันต้องรอ 5 วินาทีซึ่งโดยทั่วไปจะเพียงพอสำหรับหน้าเพื่อโหลดเนื้อหาที่สร้างขึ้นใหม่ให้เสร็จ แต่นี่อาจไม่ได้ผลเวลา หน้าอาจโหลดเนื้อหาใหม่ภายใน 5 วินาที ฉันจะตรวจสอบได้อย่างไรว่าหน้าโหลดเนื้อหาใหม่ทุกครั้งที่เลื่อนลงหรือไม่ หากฉันสามารถตรวจจับสิ่งนี้ได้ฉันสามารถเลื่อนลงมาอีกครั้งเพื่อดูเนื้อหาเพิ่มเติมเมื่อฉันรู้ว่าเพจโหลดเสร็จแล้ว นี่คือเวลาที่มีประสิทธิภาพมากขึ้น


1
มันอาจช่วยให้รู้เพิ่มเติมเกี่ยวกับหน้าเล็กน้อย องค์ประกอบต่าง ๆ เป็นลำดับหรือคาดการณ์ได้หรือไม่? คุณสามารถรอองค์ประกอบที่จะโหลดโดยการตรวจสอบการมองเห็นโดยใช้ id หรือ xpath
2272115

ฉันกำลังรวบรวมข้อมูลหน้าต่อไปนี้: pinterest.com/cremedelacrumb/yum
apogne


สิ่งนี้ตอบคำถามของคุณหรือไม่ รอโหลดหน้าเว็บใน Selenium
Matej J

คำตอบ:


234

webdriverจะรอหน้าโหลดโดยค่าเริ่มต้นผ่านทาง.get()วิธีการ

เนื่องจากคุณอาจมองหาองค์ประกอบบางอย่างตามที่ @ user227215 กล่าวไว้คุณควรใช้WebDriverWaitเพื่อรอองค์ประกอบที่อยู่ในหน้าของคุณ:

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

ฉันใช้เพื่อตรวจสอบการแจ้งเตือน คุณสามารถใช้วิธีอื่น ๆ เพื่อค้นหาที่ตั้ง

แก้ไข 1:

ฉันควรพูดถึงว่าwebdriverจะรอให้หน้าโหลดตามค่าเริ่มต้น ไม่ต้องรอโหลดภายในเฟรมหรือคำขอ ajax หมายความว่าเมื่อคุณใช้.get('url')เบราว์เซอร์ของคุณจะรอจนกว่าหน้าจะโหลดเสร็จสมบูรณ์แล้วไปที่คำสั่งถัดไปในรหัส แต่เมื่อคุณโพสต์คำขอ ajax webdriverไม่ต้องรอและเป็นความรับผิดชอบของคุณในการรอเวลาที่เหมาะสมสำหรับหน้าเว็บหรือส่วนหนึ่งของหน้าโหลด expected_conditionsจึงมีการตั้งชื่อโมดูล


3
ผมได้รับ "find_element () อาร์กิวเมนต์หลังจาก * ต้องเป็นลำดับไม่ WebElement" เปลี่ยนเป็น "WebDriverWait (เบราว์เซอร์ล่าช้า) .until (EC.presence_of_element_located ((By.ID 'IdOfMyElement')))" ดูที่คู่มือselenium- python.readthedocs.org/en/latest/waits.html
Fragles

2
ความคิดเห็นโดย @fragles และคำตอบของ David Cullen เป็นสิ่งที่ใช้ได้ผลสำหรับฉัน บางทีคำตอบที่ได้รับการยอมรับนี้อาจได้รับการปรับปรุงให้เหมาะสม
Michael Ohlrogge

6
การส่งผ่านbrowser.find_element_by_id('IdOfMyElement')ทำให้ a NoSuchElementExceptionถูกยก เอกสารกล่าวว่าจะผ่าน tuple (By.ID, 'IdOfMyElement')ที่มีลักษณะเช่นนี้: ดูคำตอบของฉัน
David Cullen

2
หวังว่าสิ่งนี้จะช่วยคนอื่นเพราะฉันไม่ชัดเจนในตอนแรก: WebDriverWait จะคืนค่าวัตถุเว็บที่คุณสามารถดำเนินการกับ (เช่นclick()) อ่านข้อความออกจาก ฯลฯ ฉันรู้สึกผิดที่มันผิด ทำให้เกิดการรอหลังจากที่คุณยังคงต้องหาองค์ประกอบ หากคุณรอสักครู่แล้วค้นหาองค์ประกอบหลังจากนั้นซีลีเนียมจะผิดพลาดเพราะพยายามค้นหาองค์ประกอบในขณะที่การรอเก่ายังคงดำเนินการอยู่ บรรทัดล่างคือคุณไม่จำเป็นต้องค้นหาองค์ประกอบหลังจากใช้ WebDriverWait - มันเป็นวัตถุอยู่แล้ว
Ben Wilson

1
@Gopgop ว้าวนี่น่าเกลียดมาก ๆไม่ใช่ความคิดเห็นที่สร้างสรรค์ น่าเกลียดเกี่ยวกับมันคืออะไร? มันจะดีขึ้นได้อย่างไร
Modus Tollens

72

พยายามที่จะส่งผ่านfind_element_by_idไปยังตัวสร้างสำหรับpresence_of_element_located(ดังแสดงในคำตอบที่ยอมรับ ) ทำให้เกิดNoSuchElementExceptionขึ้น ฉันต้องใช้ไวยากรณ์ในความคิดเห็น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"

นี้ตรงกับตัวอย่างในเอกสาร นี่คือการเชื่อมโยงไปยังเอกสารโดย


2
ขอบคุณ! ใช่นี่เป็นสิ่งจำเป็นสำหรับฉันเช่นกัน ID ไม่ใช่แอตทริบิวต์เท่านั้นที่สามารถใช้เพื่อรับรายการทั้งหมดให้ใช้วิธีใช้ (ตาม) เช่นฉันใช้EC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge

นั่นเป็นวิธีที่เหมาะกับฉันเช่นกัน! ฉันเขียนคำตอบเพิ่มเติมเพิ่มเติมเกี่ยวกับตัวระบุตำแหน่งที่แตกต่างที่มีอยู่ในByวัตถุ
J0ANMM

ฉันได้โพสต์คำถามติดตามผลเกี่ยวกับความคาดหวังที่หน้าต่างๆอาจถูกโหลดและไม่เสมอไปในหน้าเดียวกัน: stackoverflow.com/questions/51641546/…
Liquidgenius

48

ค้นหาวิธีการด้านล่าง 3 วิธี:

readyState

การตรวจสอบหน้า readyState (ไม่น่าเชื่อถือ):

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'

wait_forฟังก์ชั่นผู้ช่วยที่เป็นสิ่งที่ดี แต่โชคร้ายที่click_through_to_new_pageจะเปิดให้สภาพการแข่งขันที่เราจัดการเพื่อรันสคริปต์ในหน้าเก่าก่อนที่เบราว์เซอร์ได้เริ่มต้นการประมวลผลการคลิกและpage_has_loadedเพียงแค่ส่งกลับออกไปตรงที่แท้จริง

id

การเปรียบเทียบรหัสหน้าใหม่กับรหัสเก่า:

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

เป็นไปได้ว่าการเปรียบเทียบรหัสไม่ได้มีประสิทธิภาพเท่ากับการรอข้อยกเว้นการอ้างอิงค้าง

staleness_of

ใช้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))

สำหรับรายละเอียดเพิ่มเติมตรวจสอบบล็อกของแฮร์รี่


ทำไมคุณถึงพูดแบบนั้นself.driver.execute_script('return document.readyState;')ไม่น่าเชื่อถือ? ดูเหมือนว่าจะทำงานได้อย่างสมบูรณ์แบบสำหรับกรณีการใช้งานของฉันซึ่งกำลังรอให้ไฟล์แบบสแตติกโหลดในแท็บใหม่ (ซึ่งเปิดผ่านจาวาสคริปต์ในแท็บอื่นแทน. get ())
Arthur Hebert

1
@ArthurHebert อาจไม่น่าเชื่อถือเนื่องจากสภาพการแข่งขันฉันได้เพิ่มการอ้างอิงที่เกี่ยวข้อง
kenorb

23

ดังที่ได้กล่าวไว้ในคำตอบจาก David Cullenฉันเห็นคำแนะนำเสมอที่จะใช้บรรทัดดังตัวอย่างต่อไปนี้:

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

มันยากสำหรับฉันที่จะหาที่ตั้งที่เป็นไปได้ทั้งหมดที่สามารถใช้ได้กับByดังนั้นฉันคิดว่ามันจะมีประโยชน์ในการจัดทำรายการที่นี่ อ้างอิงจากWeb Scraping with Pythonโดย Ryan Mitchell:

ID

ใช้ในตัวอย่าง; ค้นหาองค์ประกอบโดยใช้แอตทริบิวต์ HTML id

CLASS_NAME

ใช้เพื่อค้นหาองค์ประกอบตามแอตทริบิวต์คลาส HTML ทำไมฟังค์ชั่นนี้CLASS_NAMEไม่ได้เป็นแบบง่ายๆCLASS? การใช้แบบฟอร์มobject.CLASS จะสร้างปัญหาให้กับไลบรารี Java ของ Selenium ซึ่ง.classเป็นวิธีที่สงวนไว้ เพื่อให้ไวยากรณ์ซีลีเนียมสอดคล้องกันระหว่างภาษาต่าง ๆCLASS_NAMEถูกนำมาใช้แทน

CSS_SELECTOR

พบว่าองค์ประกอบโดยระดับ ID หรือชื่อแท็กของพวกเขาโดยใช้#idName, .className, tagNameการประชุม

LINK_TEXT

ค้นหาแท็ก HTML ตามข้อความที่มี ยกตัวอย่างเช่นการเชื่อมโยงที่ระบุว่า "ถัดไป" (By.LINK_TEXT, "Next")สามารถเลือกใช้

PARTIAL_LINK_TEXT

คล้ายกับLINK_TEXTแต่ตรงกับสตริงบางส่วน

NAME

ค้นหาแท็ก HTML ตามแอตทริบิวต์ชื่อ สิ่งนี้มีประโยชน์สำหรับรูปแบบ HTML

TAG_NAME

ค้นหาแท็ก HTML ตามชื่อแท็ก

XPATH

ใช้นิพจน์ XPath ... เพื่อเลือกองค์ประกอบที่ตรงกัน


5
เอกสารโดยแสดงรายการแอตทริบิวต์ที่สามารถนำมาใช้เป็นตัวชี้
David Cullen

1
นั่นคือสิ่งที่ฉันกำลังมองหา! ขอบคุณ! ตอนนี้ควรหาได้ง่ายกว่านี้เพราะ google ส่งฉันมาที่คำถามนี้ แต่ไม่ใช่เอกสารทางการ
J0ANMM

ขอบคุณสำหรับการอ้างอิงจากหนังสือ มันชัดเจนกว่าเอกสารมาก
ZygD


11

ในบันทึกย่อด้านข้างแทนที่จะเลื่อนลง 100 ครั้งคุณสามารถตรวจสอบว่าไม่มีการแก้ไข DOM อีกหรือไม่ (ในกรณีด้านล่างของหน้าเว็บเป็น 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

สิ่งนี้มีประโยชน์ อย่างไรก็ตาม 500 หมายถึงอะไร มันใหญ่พอที่จะไปที่ส่วนท้ายของหน้า?
Moondra

มันคือจำนวนหน้าควรเลื่อน ... คุณควรตั้งให้สูงที่สุด ฉันเพิ่งค้นพบว่าจำนวนนี้ก็เพียงพอแล้วสำหรับฉันเพราะมันทำให้เลื่อนหน้าจนถึงด้านล่างจนองค์ประกอบ AJAX ที่ขี้เกียจโหลดกระตุ้นจำเป็นต้องโหลดหน้าอีกครั้ง
raffaem

สิ่งนี้จะช่วยเมื่อพยายามตรวจสอบให้แน่ใจว่าความคิดเห็นทั้งหมดเกี่ยวกับปัญหาใน gitlab จะถูกโหลดอย่างสมบูรณ์
bgStack15

7

คุณเคยลองdriver.implicitly_waitไหม มันเป็นเหมือนการตั้งค่าสำหรับไดรเวอร์ดังนั้นคุณจะเรียกมันเพียงครั้งเดียวในเซสชั่นและโดยทั่วไปจะบอกให้คนขับรอจำนวนเวลาที่กำหนดจนกว่าแต่ละคำสั่งจะสามารถดำเนินการได้

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

ดังนั้นหากคุณตั้งเวลารอ 10 วินาทีมันจะดำเนินการคำสั่งโดยเร็วที่สุดรอ 10 วินาทีก่อนที่จะยอมแพ้ ฉันใช้สิ่งนี้ในสถานการณ์แบบเลื่อนลงที่คล้ายกันดังนั้นฉันจึงไม่เห็นสาเหตุที่มันไม่ทำงานในกรณีของคุณ หวังว่านี่จะเป็นประโยชน์

เพื่อให้สามารถแก้ไขคำตอบนี้ฉันต้องเพิ่มข้อความใหม่ ให้แน่ใจว่าจะใช้กรณีที่ต่ำกว่า 'w' implicitly_waitใน


ความแตกต่างระหว่างรอโดยปริยายและ webdriverwait คืออะไร?
song0089

4

วิธีการเกี่ยวกับการใส่ WebDriverWait ในขณะวนซ้ำและจับข้อยกเว้น

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"

คุณไม่ต้องการห่วง
Corey Goldberg

4

ที่นี่ฉันทำมันโดยใช้รูปแบบที่ค่อนข้างง่าย:

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

คุณสามารถทำสิ่งนี้ได้ง่ายๆด้วยฟังก์ชั่นนี้:

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

และเมื่อคุณต้องการทำบางสิ่งหลังจากโหลดหน้าเสร็จคุณสามารถใช้:

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')")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.