Đợi cho đến khi trang được tải với Selenium WebDriver cho Python


180

Tôi muốn cạo tất cả dữ liệu của một trang được thực hiện bằng một cuộn vô hạn. Mã python sau đây hoạt động.

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

Điều này có nghĩa là mỗi lần tôi cuộn xuống phía dưới, tôi cần đợi 5 giây, thường là đủ để trang hoàn tất tải nội dung mới được tạo. Nhưng, điều này có thể không hiệu quả về thời gian. Trang có thể tải xong nội dung mới trong vòng 5 giây. Làm cách nào để phát hiện xem trang đã tải xong nội dung mới mỗi lần tôi cuộn xuống chưa? Nếu tôi có thể phát hiện ra điều này, tôi có thể cuộn xuống một lần nữa để xem thêm nội dung khi tôi biết trang đã tải xong. Đây là thời gian hiệu quả hơn.


1
Nó có thể giúp để biết thêm một chút về trang. Là các yếu tố tuần tự hoặc dự đoán? Bạn có thể đợi các phần tử tải bằng cách kiểm tra độ nhớt bằng cách sử dụng id hoặc xpath
user2272115

Tôi đang thu thập dữ liệu trang sau: pinterest.com/cremedelacrumb/yum
apogne


Điều này có trả lời câu hỏi của bạn không? Đợi tải trang trong Selenium
Matej J

Câu trả lời:


233

Các webdriversẽ chờ đợi cho một trang để tải theo mặc định thông qua .get()phương pháp.

Như bạn có thể đang tìm kiếm một số yếu tố cụ thể như @ user227215 đã nói, bạn nên sử dụng WebDriverWaitđể chờ đợi một yếu tố nằm trong trang của mình:

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

Tôi đã sử dụng nó để kiểm tra cảnh báo. Bạn có thể sử dụng bất kỳ phương pháp loại nào khác để tìm trình định vị.

CHỈNH SỬA 1:

Tôi nên đề cập rằng webdriversẽ chờ một trang tải theo mặc định. Nó không chờ để tải bên trong khung hoặc cho các yêu cầu ajax. Điều đó có nghĩa là khi bạn sử dụng .get('url'), trình duyệt của bạn sẽ đợi cho đến khi trang được tải hoàn toàn và sau đó chuyển đến lệnh tiếp theo trong mã. Nhưng khi bạn đăng một yêu cầu ajax, webdriverđừng chờ đợi và bạn có trách nhiệm chờ một khoảng thời gian thích hợp để trang hoặc một phần của trang được tải; vì vậy có một mô-đun được đặt tên expected_conditions.


3
Tôi đã nhận được "find_element () lập luận sau * phải là một chuỗi, không WebElement" đổi thành "WebDriverWait (trình duyệt, chậm trễ) .until (EC.presence_of_element_located ((By.ID, "IdOfMyElement")))" xem hướng dẫn sử selenium- python.readthedocs.org/en/latest/waits.html
mảnh vỡ

2
Nhận xét của @fragles và câu trả lời của David Cullen là những gì làm việc cho tôi. Có lẽ câu trả lời được chấp nhận này có thể được cập nhật cho phù hợp?
Michael Ohlrogge

6
Vượt qua browser.find_element_by_id('IdOfMyElement')khiến a NoSuchElementExceptionđược nâng lên. Các tài liệu nói để vượt qua một tuple trông như thế này : (By.ID, 'IdOfMyElement'). Xem câu trả lời của tôi
David Cullen

2
Hy vọng rằng điều này sẽ giúp người khác hiểu vì ban đầu tôi không rõ: WebDriverWait thực sự sẽ trả về một đối tượng web mà sau đó bạn có thể thực hiện một hành động trên (ví dụ click()), đọc văn bản ra khỏi vv. Tôi đã bị ấn tượng nhầm rằng nó chỉ là gây ra sự chờ đợi, sau đó bạn vẫn phải tìm phần tử. Nếu bạn chờ đợi, sau đó một phần tử tìm thấy, selen sẽ báo lỗi vì nó cố gắng tìm phần tử trong khi chờ đợi cũ vẫn đang xử lý (hy vọng điều đó có ý nghĩa). Điểm mấu chốt là, bạn không cần tìm phần tử sau khi sử dụng WebDriverWait - nó đã là một đối tượng.
Ben Wilson

1
@Gopgop Wow điều này thật xấu xí không phải là một nhận xét mang tính xây dựng. Điều gì là xấu xí về nó? Làm thế nào nó có thể được làm tốt hơn?
Modus Tollens

72

Cố gắng chuyển find_element_by_idđến hàm tạo cho presence_of_element_located(như thể hiện trong câu trả lời được chấp nhận ) gây ra NoSuchElementExceptionđược nêu ra. Tôi đã phải sử dụng cú pháp trong bình luận của 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"

Điều này phù hợp với ví dụ trong tài liệu . Đây là một liên kết đến các tài liệu cho By .


2
Cảm ơn bạn! vâng, điều này cũng cần thiết cho tôi ID không phải là thuộc tính duy nhất có thể được sử dụng, để có danh sách đầy đủ, hãy sử dụng trợ giúp (Theo). Ví dụ: tôi đã sử dụngEC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge

Đó cũng là cách nó làm việc với tôi! Tôi đã viết một câu trả lời bổ sung mở rộng trên các bộ định vị khác nhau có sẵn với Byđối tượng.
J0ANMM

Tôi đã đăng một câu hỏi tiếp theo liên quan đến các kỳ vọng nơi các trang khác nhau có thể được tải và không phải lúc nào cũng là cùng một trang: stackoverflow.com/questions/51641546/
Kẻ

48

Tìm bên dưới 3 phương pháp:

sẵn sàng

Kiểm tra trang readyState (không đáng tin cậy):

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'

Các wait_forchức năng helper là tốt, nhưng tiếc click_through_to_new_pagelà mở cửa cho tình trạng chủng tộc, nơi chúng tôi quản lý để thực thi kịch bản trong trang cũ, trước khi trình duyệt đã bắt đầu xử lý các nhấp chuột, và page_has_loadedchỉ trả về true ngay lập tức.

id

So sánh id trang mới với id cũ:

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

Có thể việc so sánh id không hiệu quả bằng việc chờ đợi các ngoại lệ tham chiếu cũ.

staleness_of

Sử dụng staleness_ofphương pháp:

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

Để biết thêm chi tiết, kiểm tra blog của Harry .


Tại sao bạn nói rằng self.driver.execute_script('return document.readyState;')không đáng tin cậy? Nó dường như hoạt động hoàn hảo cho trường hợp sử dụng của tôi, đang chờ một tệp tĩnh tải trong một tab mới (được mở thông qua javascript trong một tab khác thay vì .get ()).
Arthur Hebert

1
@ArthurHebert Có thể không đáng tin cậy do điều kiện chủng tộc, tôi đã thêm trích dẫn có liên quan.
kenorb

23

Như đã đề cập trong câu trả lời từ David Cullen , tôi luôn thấy các đề xuất nên sử dụng một dòng như sau:

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

Thật khó cho tôi để tìm thấy ở đâu đó tất cả các trình định vị có thể có thể được sử dụng với By, vì vậy tôi nghĩ rằng sẽ hữu ích khi cung cấp danh sách ở đây. Theo Web Scraping với Python của Ryan Mitchell:

ID

Được sử dụng trong ví dụ; tìm các phần tử theo thuộc tính id HTML của chúng

CLASS_NAME

Được sử dụng để tìm các phần tử theo thuộc tính lớp HTML của chúng. Tại sao chức năng này CLASS_NAMEkhông đơn giản CLASS? Sử dụng biểu mẫu object.CLASS sẽ tạo ra các vấn đề cho thư viện Java của Selenium, đây .classlà một phương thức dành riêng. Để giữ cho cú pháp Selen phù hợp giữa các ngôn ngữ khác nhau, CLASS_NAMEđã được sử dụng thay thế.

CSS_SELECTOR

Phát hiện các yếu tố của lớp, id hoặc tên thẻ của họ, sử dụng #idName, .className, tagNamehội nghị.

LINK_TEXT

Tìm các thẻ HTML theo văn bản mà chúng chứa. Ví dụ: một liên kết có nội dung "Tiếp theo" có thể được chọn bằng cách sử dụng (By.LINK_TEXT, "Next").

PARTIAL_LINK_TEXT

Tương tự LINK_TEXT, nhưng khớp trên một phần chuỗi.

NAME

Tìm thẻ HTML theo thuộc tính tên của chúng. Điều này rất hữu ích cho các hình thức HTML.

TAG_NAME

Tìm thẻ HTML theo tên thẻ của họ.

XPATH

Sử dụng biểu thức XPath ... để chọn các phần tử phù hợp.


5
Các tài liệu cho Bằng cách liệt kê các thuộc tính có thể được sử dụng như định vị.
David Cullen

1
Đó là những gì tôi đã tìm kiếm! Cảm ơn! Chà, bây giờ nó sẽ dễ tìm hơn vì google đã gửi cho tôi câu hỏi này, nhưng không phải là tài liệu chính thức.
J0ANMM

Cảm ơn các trích dẫn từ cuốn sách. Nó rõ ràng hơn nhiều so với tài liệu.
ZygD


11

Ở một ghi chú bên cạnh, thay vì cuộn xuống 100 lần, bạn có thể kiểm tra xem không có sửa đổi nào nữa đối với DOM hay không (chúng tôi đang ở trong trường hợp phần dưới cùng của trang bị tải 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

Điều này rất hữu ích. Tuy nhiên, 500 đại diện cho những gì? Nó có đủ lớn để đi đến cuối trang không?
Moondra

Đó là số lượng trang sẽ cuộn ... bạn nên đặt nó càng cao càng tốt. Tôi chỉ phát hiện ra rằng con số này là đủ cho tôi, vì nó làm cho trang cuộn xuống phía dưới cho đến khi các phần tử AJAX được tải một cách lười biếng, thúc đẩy nhu cầu tải lại trang một lần nữa
raffaem

Điều này giúp khi cố gắng đảm bảo tất cả các nhận xét về một vấn đề trong gitlab được tải đầy đủ.
bgStack15

7

Các bạn đã thử driver.implicitly_wait. Nó giống như một cài đặt cho trình điều khiển, vì vậy bạn chỉ gọi nó một lần trong phiên và về cơ bản nó sẽ cho trình điều khiển đợi khoảng thời gian nhất định cho đến khi mỗi lệnh có thể được thực thi.

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

Vì vậy, nếu bạn đặt thời gian chờ là 10 giây, nó sẽ thực thi lệnh càng sớm càng tốt, đợi 10 giây trước khi nó từ bỏ. Tôi đã sử dụng điều này trong các tình huống cuộn xuống tương tự vì vậy tôi không hiểu tại sao nó không hoạt động trong trường hợp của bạn. Hy vọng điều này là hữu ích.

Để có thể sửa câu trả lời này, tôi phải thêm văn bản mới. Hãy chắc chắn sử dụng chữ thường 'w' in implicitly_wait.


Sự khác biệt giữa chờ đợi ngầm và webdowait là gì?
song0089

4

Làm thế nào về việc đưa WebDriverWait vào vòng lặp While và nắm bắt các ngoại lệ.

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"

bạn không cần vòng lặp?
Corey Goldberg

4

Ở đây tôi đã làm nó bằng một hình thức khá đơn giản:

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

Bạn có thể làm điều đó rất đơn giản bằng chức năng này:

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

và khi bạn muốn làm gì đó sau khi tải trang xong, bạn có thể sử dụng:

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')")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.