TransWikia.com

Problema ao navegar com Selenium (usando Python) em resultados de uma busca apresentados em HTML dinâmico

Stack Overflow em Português Asked by Victor Heuer on September 27, 2021

Estou realizando um scraping de matérias de um jornal pernambucano (Diário de PE) conforme uma busca que fiz com algumas palavras-chave sobre o assunto de interesse. A busca do jornal retorna 10 resultados em uma página com HTML dinamico, contendo, ao final, uma lista com todas as paginas de resultados, numeradas de 1 a 10.

Quanto à 1ª pagina, eu consegui fazer o scraping do trecho que desejo do HTML utilizando o Selenium em Python sem grandes problemas, mas estou tendo problemas para acessar as demais páginas a partir da lista gerada.

As referências para as próximas páginas estão em tags <div class="gsc-cursor-page" aria-label="Página 2" role="link" tabindex="0">2</div>(tomando como exemplo a referência para a 2ª pg.). Não há uma tag âncora <a> associada a um href gerando o hiperlink para a próxima pagina e nem um botão "próxima". Vide um exemplo disso na imagem abaixo.

inserir a descrição da imagem aqui

Minha estratégia: criei uma função que pegar as referências das páginas nessas divs dando os caminhos através do XPATH. Essas referências foram armazenadas em uma lista, e criei um laço ‘for’ para iterar referencia após referência, utilizando a função click() do Selenium para acessá-las. Criei essa estratégia baseado em várias dicas dadas no próprio Stackoverflow (versão em inglês) e infelizmente ela não funcionou, e só a 2ª página é retornada (além da 1ª).

Abaixo estão os código das duas funções que criei, a primeira para pegar o HTML da 1ª página, e a segunda para fazer isso nas demais, usando a lista que comentei.

Bibliotecas importadas:

# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
import time

Primeira função para fazer o scraping do HTML da área desejada na página inicial de resultados da busca:

def get_target_html(url):
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    driver = webdriver.Chrome(options=chrome_options)
    wait = WebDriverWait(driver, 10)
    driver.get(url)
    #Finding target HTML
    site_html = wait.until(lambda driver: driver.find_element_by_xpath('//div[@class="gsc-expansionArea"]').get_attribute('innerHTML'))
    driver.close() 
    with open('target_page_1.html', 'wt', encoding='utf-8') as file:
        file.write(site_html)

Segunda função para navegar para as demais páginas e fazer o scraping do HTML:

def get_next_pages(url): 
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    driver = webdriver.Chrome(options=chrome_options)
    driver.get(url)
    #Creating a list that receives all elements according to the given XPATH
    page_list = driver.find_elements_by_xpath('//div[@class="gsc-cursor-page"]')
    #Reading each element in the list to access the related page and get the target HTML code
    count = 1
    for page in page_list:
        count += 1
        number = str(count)
        page.click()
        time.sleep(10)
        target_html = driver.find_element_by_xpath('//div[@class="gsc-expansionArea"]').get_attribute('innerHTML')
        #Writing and saving a html file with the target code
        with open(f'target_page_{number}.html', 'wt', encoding='utf-8') as file:
            file.write(target_html)
    driver.close()
    print('Finished')

Em seguida, chamei as duas funções fornecendo a URL inicial (https://www.diariodepernambuco.com.br/capa_busca.html?q=seguran%C3%A7a&q=seguran%C3%A7a%20%2B%20p%C3%BAblica%20%2B%20pernambuco&q=%22seguran%C3%A7a%20p%C3%BAblica%22%20%2B%20pernambuco&q=%22seguran%C3%A7a%20p%C3%BAblica%22%20%2B%20crime*%20%2B%20pernambuco).

A excessão que foi retornada:

 File "C:UsersVictorOneDriveScrapy ProjectsCorpus_Jornaisteste_selenium_diariodepernambuco5.py", line 57, in <module>
    get_next_pages(url)

  File "C:UsersVictorOneDriveScrapy ProjectsCorpus_Jornaisteste_selenium_diariodepernambuco5.py", line 40, in get_next_pages
    page.click()

  File "C:UsersVictoranaconda3envspy36libsite-packagesseleniumwebdriverremotewebelement.py", line 80, in click
    self._execute(Command.CLICK_ELEMENT)

  File "C:UsersVictoranaconda3envspy36libsite-packagesseleniumwebdriverremotewebelement.py", line 633, in _execute
    return self._parent.execute(command, params)

  File "C:UsersVictoranaconda3envspy36libsite-packagesseleniumwebdriverremotewebdriver.py", line 321, in execute
    self.error_handler.check_response(response)

  File "C:UsersVictoranaconda3envspy36libsite-packagesseleniumwebdriverremoteerrorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)

StaleElementReferenceException: stale element reference: element is not attached to the page document
  (Session info: headless chrome=84.0.4147.89)

Entendo que através dessa excessão StaleElementReferenceException, é informado que o objeto desejado para o click() não está mais disponível, contudo creio que ele esteja na lista para ser acessado. Rodei o código da 2ª função em isolado, e depois pedi o tamanho da lista (len(page_list)), retornando a quantidade exata de referências que foram coletadas (=9).

Alguém teria alguma dica sobre como lidar com esse problema?

One Answer

Consegui achar uma solução pesquisando mais sobre o que estava acontecendo. Encontrei aqui no Stackoverflor em português uma resposta que me ajudou a estruturar o que eu precisava resolver. Para aqueles que se depararem com esse tipo de problema, segue:

Primeiramente, recomendo a leitura da resposta dada à essa questão: Problemas de StaleElementReferenceException em Selenium com Python

Ela determina, em síntese, que se aquela exceção aparece é porque o elemento que o click() está tentando acessar foi perdido.

Construí a solução com base na ideia de que é preciso atualizar a lista a cada vez que seja feito um acesso à página na sequencia. A primeira função que criei foi mantida. A segunda ficou assim:

def get_next_pages(url): 
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    driver = webdriver.Chrome(options=chrome_options)
    driver.get(url)
    #Creating a list that receives all elements according to the given XPATH
    page_list = driver.find_elements_by_xpath('//div[@class="gsc-cursor-page"]')
    pages_amount = len(page_list)
    #Creating a loop to update the pages list and get their target HTML
    for index in range(pages_amount):
        number = str(index+2)
        updated_pages_list = driver.find_elements_by_xpath('//div[@class="gsc-cursor-page"]')
        print(f'Accessing page {number}')
        updated_page_list[index].click()
        time.sleep(5)
        target_html = driver.find_element_by_xpath('//div[@class="gsc-expansionArea"]').get_attribute('innerHTML')
        #Writing and saving a HTML file with the target code
        print(f'Saving page {number} html')
        with open(f'target_page_{number}.html', 'wt', encoding='utf-8') as file:
            file.write(target_html)
    driver.close()
    print('Finished')

Explicando o que eu alterei:

No inicio da função ainda é criada uma lista de páginas (page_list), mas dessa vez, só como um referêncial para que possa ser calculado a quantidade de elementos nele (page_amount).

Com esse tamanho definido, pode-se realizar um 'for', onde em cada nova iteracão é criada uma nova lista de páginas (updated_page_list), acessando o elemento na posição indexada de acordo com a iteração.

Eu acrescentei alguns prints para ter uma visão daquilo que estava sendo feito, usando o number = str(index+2) para ter a referência ajustada das impressões em tela de acordo com a forma como a lista aparece originalmente no site (de 1 a 10, e não de 0 a 9).

Finalmente, os HTML alvos de cada uma das paginas listadas é raspado e salvo em arquivo numerado também de acordo com a variável "number".

Caso algum interessado tenha ficado com duvida em relação a essa resposta, é só falar aqui que terei o prazer de reiterá-la. E caso alguém tenha uma solução melhor, mais elegante, fica o espaço para apresentá-la.

Minhas saudações!

Correct answer by Victor Heuer on September 27, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP