Aguarde até a página ser carregada com o Selenium WebDriver for Python

181

Quero raspar todos os dados de uma página implementada por uma rolagem infinita. O seguinte código python funciona.

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

Isso significa que toda vez que eu rolar para baixo, preciso esperar 5 segundos, o que geralmente é suficiente para que a página termine de carregar o conteúdo recém-gerado. Mas, isso pode não ser eficiente em termos de tempo. A página pode terminar de carregar o novo conteúdo em 5 segundos. Como posso detectar se a página terminou de carregar o novo conteúdo toda vez que rolar para baixo? Se eu conseguir detectar isso, posso rolar para baixo novamente para ver mais conteúdos quando souber que a página terminou de carregar. Isso é mais eficiente em termos de tempo.

apogne
fonte
1
Talvez seja útil conhecer um pouco mais sobre a página. Os elementos são seqüenciais ou previsíveis? Você pode esperar o carregamento dos elementos verificando a visibilidade usando id ou xpath
user2272115
Estou rastreando a seguinte página: pinterest.com/cremedelacrumb/yum
apogne
Isso responde sua pergunta? Aguarde o carregamento da página em Selenium
Matej J

Respostas:

234

Ele webdriveraguardará o carregamento de uma página por padrão, através do .get()método

Como você pode estar procurando por algum elemento específico como @ user227215 disse, você deve WebDriverWaitaguardar um elemento localizado em sua página:

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

Eu o usei para verificar alertas. Você pode usar qualquer outro método de tipo para encontrar o localizador.

EDIT 1:

Devo mencionar que o webdriverirá esperar por uma página para carregar por padrão. Ele não espera para carregar dentro de quadros ou solicitações de ajax. Isso significa que, quando você usa .get('url'), o navegador aguarda o carregamento completo da página e passa para o próximo comando no código. Mas quando você está postando uma solicitação ajax, webdrivernão espera e é de sua responsabilidade aguardar um período de tempo apropriado para carregar a página ou parte dela; para que haja um módulo chamado expected_conditions.

Zeinab Abbasimazar
fonte
3
Eu estava recebendo o argumento "find_element () depois que * deve ser uma sequência, não o WebElement" alterado para "WebDriverWait (navegador, atraso). Até que (EC.presence_of_element_located ((By.ID," IdOfMyElement ")))" consulte o manual de selênio python.readthedocs.org/en/latest/waits.html
fragles
2
O comentário de @fragles e a resposta de David Cullen foram o que funcionou para mim. Talvez essa resposta aceita possa ser atualizada de acordo?
Michael Ohlrogge
6
Passar browser.find_element_by_id('IdOfMyElement')faz com que NoSuchElementExceptiona seja levantada. A documentação diz que passar uma tupla que se parece com isso: (By.ID, 'IdOfMyElement'). Veja minha resposta
David Cullen
2
Espero que isso ajude outra pessoa, porque inicialmente não estava claro para mim: O WebDriverWait realmente retornará um objeto da Web em que você poderá executar uma ação (por exemplo click()), ler texto de etc. Fiquei com a impressão errada de que ela simplesmente causou uma espera, após o qual você ainda precisava encontrar o elemento. Se você fizer uma espera, depois um elemento find, o selênio cometerá um erro porque tenta encontrar o elemento enquanto a espera antiga ainda está sendo processada (espero que faça sentido). Resumindo, você não precisa encontrar o elemento depois de usar o WebDriverWait - ele já é um objeto.
Ben Wilson
1
@ Gopgop Uau, isso é tão feio, não é um comentário construtivo. O que é feio nisso? Como poderia ser melhorado?
Modus Tollens
72

Tentando passar find_element_by_idpara o construtor para presence_of_element_located(como mostrado na resposta aceita ) causado NoSuchElementExceptiona ser levantada. Eu tive que usar a sintaxe no comentário dos 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"

Isso corresponde ao exemplo na documentação . Aqui está um link para a documentação de Por .

David Cullen
fonte
2
Obrigado! Sim, isso também era necessário para mim. O ID não é o único atributo que pode ser usado. Para obter a lista completa, use a ajuda (Por). Por exemplo, eu useiEC.presence_of_element_located((By.XPATH, "//*[@title='Check All Q1']"))
Michael Ohlrogge
É assim que funciona para mim também! Escrevi uma resposta adicional expandindo os diferentes localizadores disponíveis com o Byobjeto.
J0ANMM #
Eu postei uma pergunta acompanhamento lidar com as expectativas onde diferentes páginas podem ser carregadas, e nem sempre a mesma página: stackoverflow.com/questions/51641546/...
Liquidgenius
48

Encontre abaixo três métodos:

readyState

Página de verificação readyState (não confiável):

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'

A wait_forfunção auxiliar é boa, mas infelizmente click_through_to_new_pageestá aberta à condição de corrida em que conseguimos executar o script na página antiga, antes que o navegador comece a processar o clique e page_has_loadedretorne verdadeiro imediatamente.

id

Comparando novos IDs de página com os antigos:

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

É possível que comparar IDs não seja tão eficaz quanto aguardar exceções de referência obsoletas.

staleness_of

Usando o staleness_ofmétodo:

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

Para mais detalhes, consulte o blog de Harry .

kenorb
fonte
Por que você diz que self.driver.execute_script('return document.readyState;')não é confiável? Parece funcionar perfeitamente no meu caso de uso, que aguarda o carregamento de um arquivo estático em uma nova guia (que é aberta via javascript em outra guia, em vez de .get ()).
Arthur Hebert
1
@ArthurHebert Pode não ser confiável devido às condições da corrida, adicionei uma citação relevante.
Kenorb #
23

Como mencionado na resposta de David Cullen , sempre vi recomendações para usar uma linha como a seguinte:

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

Foi difícil para mim encontrar em algum lugar todos os possíveis localizadores que podem ser usados ​​com o By, então achei que seria útil fornecer a lista aqui. De acordo com Web Scraping with Python, de Ryan Mitchell:

ID

Usado no exemplo; localiza elementos por seu atributo de ID HTML

CLASS_NAME

Usado para encontrar elementos por seu atributo de classe HTML. Por que essa função CLASS_NAMEnão é simplesmente CLASS? O uso do formulário object.CLASS criaria problemas para a biblioteca Java do Selenium, onde .classé um método reservado. A fim de manter a sintaxe do Selenium consistente entre diferentes idiomas, CLASS_NAMEfoi usada em seu lugar.

CSS_SELECTOR

Encontra elementos por sua classe, id, ou nome da marca, usando o #idName, .className, tagNameconvenções.

LINK_TEXT

Localiza as tags HTML pelo texto que elas contêm. Por exemplo, um link que diz "Avançar" pode ser selecionado usando (By.LINK_TEXT, "Next").

PARTIAL_LINK_TEXT

Semelhante a LINK_TEXT, mas corresponde a uma sequência parcial.

NAME

Localiza tags HTML pelo atributo name. Isso é útil para formulários HTML.

TAG_NAME

Localiza tags HTML pelo nome da tag.

XPATH

Usa uma expressão XPath ... para selecionar elementos correspondentes.

J0ANMM
fonte
5
A documentação para Por lista os atributos que podem ser usados ​​como localizadores.
David Cullen
1
Era isso que eu estava procurando! Obrigado! Bem, agora deve ser mais fácil encontrar, pois o Google estava me enviando para essa pergunta, mas não para a documentação oficial.
J0ANMM #
Obrigado pela citação do livro. É muito mais claro que a documentação.
ZygD 17/06
11

Em uma nota lateral, em vez de rolar para baixo 100 vezes, você pode verificar se não há mais modificações no DOM (no caso da parte inferior da página estar com o carregamento lento do 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
fonte
Isso é útil. No entanto, o que os 500 representam? É grande o suficiente para chegar ao final da página?
Moondra 22/02
É a quantidade que a página deve rolar ... você deve defini-la o mais alto possível. Eu só descobri que esse número era suficiente para mim, uma vez que torna o livro página até o fundo até elementos AJAX são preguiçosos-carregado, estimulando a necessidade de re-carregar a página novamente
raffaem
Isso ajuda ao tentar garantir que todos os comentários sobre um problema no gitlab estejam totalmente carregados.
bgStack15
7

Você já tentou driver.implicitly_wait? É como uma configuração para o driver, então você a chama apenas uma vez na sessão e basicamente diz ao driver que aguarde o tempo determinado até que cada comando possa ser executado.

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

Portanto, se você definir um tempo de espera de 10 segundos, ele executará o comando o mais rápido possível, aguardando 10 segundos antes de desistir. Eu usei isso em cenários de rolagem para baixo semelhantes, então não vejo por que não funcionaria no seu caso. Espero que isso seja útil.

Para poder corrigir esta resposta, tenho que adicionar um novo texto. Certifique-se de usar uma letra minúscula 'w' implicitly_wait.

seeiespi
fonte
Qual é a diferença entre implicitamente wait e webdriverwait?
song0089 11/06
4

Que tal colocar o WebDriverWait no loop While e capturar as exceções.

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
fonte
você não precisa do loop?
Corey Goldberg #
4

Aqui eu fiz isso usando um formulário bastante simples:

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
fonte
1

Você pode fazer isso de maneira muito simples com esta função:

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

e quando você quiser fazer algo depois que o carregamento da página estiver concluído, você pode usar:

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
fonte