Selenium WebDriver for Pythonでページが読み込まれるまで待ちます


180

無限スクロールで実装されたページのデータをすべて削り取りたい。次のpythonコードが機能します。

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

つまり、一番下までスクロールするたびに5秒待つ必要があります。これは、ページが新しく生成されたコンテンツの読み込みを完了するのに十分な時間です。しかし、これは時間効率が良くない場合があります。ページは5秒以内に新しいコンテンツの読み込みを完了する場合があります。下にスクロールするたびに、ページが新しいコンテンツのロードを完了したかどうかをどのように検出できますか?これを検出できた場合は、ページの読み込みが完了したことを確認したら、下にスクロールしてさらにコンテンツを表示できます。これはより時間効率が良いです。


1
ページについてもう少し知っておくと役立ちます。要素は連続的ですか、予測可能ですか?あなたはIDまたはXPathを使用して可視性をチェックすることで、負荷への要素のために待つことができる
user2272115

次のページをクロールしています:pinterest.com/cremedelacrumb/yum
apogne 2014年


これはあなたの質問に答えますか?Seleniumでのページの読み込みを待つ
Matej J

回答:


233

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
「WebElementではなくシーケンスである必要があります」の後に「find_element()引数」を取得しましたが、「WebDriverWait(browser、delay).until(EC.presence_of_element_located((By.ID、 "IdOfMyElement")))」に変更されました。手動のセレンを
fragles

2
@fraglesのコメントとDavid Cullenの答えが私にとってうまくいきました。おそらく、この受け入れられた回答はそれに応じて更新できますか?
Michael Ohlrogge、2016年

6
渡すbrowser.find_element_by_id('IdOfMyElement')とa NoSuchElementExceptionが発生します。ドキュメントには、次のようになりますタプルを渡すために言います:(By.ID, 'IdOfMyElement')私の回答を
デビッドカレン

2
最初ははっきりしていなかったので、うまくいけば他の人を助けてくれるといいのですが。WebDriverWaitは実際にWebオブジェクトを返します(たとえばclick())でアクションを実行したり、テキストを読み取ったりすることができます。待機を引き起こしましたが、その後、要素を見つける必要がありました。待機を行った場合、後で要素を検索すると、古い待機がまだ処理されている間に(おそらくそれが理にかなっている)セレンが要素を見つけようとするため、セレンはエラーになります。結論としては、WebDriverWaitを使用した後に要素を見つける必要はありません-既にオブジェクトです。
Ben Wilson

1
@Gopgop うわーこれはとても醜いので、建設的なコメントではありません。それについて醜い何ですか?どうすれば改善できるでしょうか?
Modus Tollens 2018

72

受け入れられた回答に示されているように)find_element_by_idのコンストラクタに渡そうとすると、発生しました。fraglesコメントで構文を使用する必要がありました:presence_of_element_locatedNoSuchElementException

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"

これは、ドキュメント例と一致しますこれはByドキュメントへのリンクです。


2
ありがとうございました!はい、これも私にとって必要でした。使用できる属性はIDだけではありません。完全なリストを取得するには、help(By)を使用してください。例:私が使用したEC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge

それは私にとっても同じように機能します!オブジェクトで使用できるさまざまなロケーターを拡張する追加の回答を書きましたBy
J0ANMM 2016年

私はいつも同じページを別のページがロードされてもよいの期待を扱うフォローアップ質問を投稿し、いませんでした: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ちょうどすぐにtrueを返します。

id

新しいページIDと古いページ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

IDの比較は、古くなった参照例外を待つほど効果的ではない可能性があります。

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()の代わりに別のタブのJavaScriptで開かれる)に読み込まれるのを待機している私のユースケースでは完全に機能するようです。
Arthur Hebert

1
@ArthurHebert競合状態により信頼できない可能性があるため、関連する引用を追加しました。
ケノーブ2018

23

David Cullen回答で述べたように、私は常に次のような行を使用することを推奨しています。

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

で使用できるすべての可能なロケーターをどこかに見つけるのは難しいByので、ここにリストを提供すると便利だと思いました。Ryan MitchellによるPythonによるWebスクレイピングによると:

ID

例で使用されています。HTML id属性で要素を検索します

CLASS_NAME

HTMLクラス属性によって要素を検索するために使用されます。なぜこの関数CLASS_NAMEは単純ではないのですCLASSか?フォームobject.CLASS を使用する.classと、予約メソッドであるSeleniumのJavaライブラリに問題が発生します。異なる言語間でSelenium構文を一貫させるために、CLASS_NAME代わりにが使用されました。

CSS_SELECTOR

使用して、自分のクラス、ID、またはタグ名で要素を検索し#idName.classNametagName大会を。

LINK_TEXT

含まれているテキストでHTMLタグを検索します。たとえば、「次へ」というリンクは、を使用して選択できます(By.LINK_TEXT, "Next")

PARTIAL_LINK_TEXT

に似てLINK_TEXTいますが、部分的な文字列に一致します。

NAME

name属性でHTMLタグを検索します。これはHTMLフォームに便利です。

TAG_NAME

タグ名でHTMLタグを検索します。

XPATH

XPath式...を使用して、一致する要素を選択します。


5
Byドキュメントには、ロケーターとして使用できる属性がリストされています。
David Cullen

1
それが私が探していたものです!ありがとう!さて、グーグルがこの質問に私を送っていたので、見つけるのはもっと簡単になるはずですが、公式のドキュメントには送られません。
J0ANMM 2016年

本からの引用をありがとう。ドキュメントよりもはるかに明確です。
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は何を表していますか?ページの最後に到達するのに十分な大きさですか?
ムーンドラ

これはページがスクロールする量です...できるだけ高く設定する必要があります。AJAX要素が遅延読み込みされるまでページが一番下までスクロールし、ページの再読み込みが必要になるため、この数で十分だとわかりました
raffaem

これは、gitlabの問題に関するすべてのコメントが完全に読み込まれるようにするときに役立ちます。
bgStack15

7

試しましたかdriver.implicitly_wait。これはドライバーの設定に似ているため、セッションで一度だけ呼び出すだけで、基本的には、各コマンドが実行されるまで一定時間待機するようにドライバーに指示します。

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

したがって、待機時間を10秒に設定すると、コマンドはできるだけ早く実行され、中止する前に10秒間待機します。私はこれを同様のスクロールダウンシナリオで使用したので、あなたのケースではそれが機能しない理由がわかりません。これがお役に立てば幸いです。

この回答を修正するには、新しいテキストを追加する必要があります。では必ず小文字の「w」を使用してくださいimplicitly_wait


暗黙の待機とwebdriverwaitの違いは何ですか?
song0089

4

WebDriverWaitをWhileループに入れて例外をキャッチするのはどうでしょう。

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.