¿Cómo puedo obtener el texto de un elemento en Selenium WebDriver, sin incluir el texto del elemento secundario?

5 minutos de lectura

avatar de usuario de josh
jose

Considerar:

<div id="a">This is some
   <div id="b">text</div>
</div>

Obtener “Esto es algo” no es trivial. Por ejemplo, esto devuelve “Esto es un texto”:

driver.find_element_by_id('a').text

¿Cómo se obtiene, de manera general, el texto de un elemento específico sin incluir el texto de sus hijos?

  • Entonces, para que conste, lo que terminé haciendo fue hacerlo en javascript… Tengo jQuery en las páginas que estoy probando, así que aproveché el hecho de que Selenium convierte automáticamente los elementos dom devueltos por javascript en WebElements: my_result = driver .execute_script(‘volver […call to my jquery function..]’)

    – jose

    10 de septiembre de 2012 a las 19:03

Avatar de usuario de Louis
Luis

Aquí hay una solución general:

def get_text_excluding_children(driver, element):
    return driver.execute_script("""
    return jQuery(arguments[0]).contents().filter(function() {
        return this.nodeType == Node.TEXT_NODE;
    }).text();
    """, element)

El elemento pasado a la función puede ser algo obtenido de la find_element...() métodos (es decir, puede ser un WebElement objeto).

O si no tiene jQuery o no quiere usarlo, puede reemplazar el cuerpo de la función anterior con esto:

return self.driver.execute_script("""
var parent = arguments[0];
var child = parent.firstChild;
var ret = "";
while(child) {
    if (child.nodeType === Node.TEXT_NODE)
        ret += child.textContent;
    child = child.nextSibling;
}
return ret;
""", element)

De hecho, estoy usando este código en un conjunto de pruebas.

  • bien, lo que básicamente me di cuenta es … no use los métodos de búsqueda de Selenium, solo use jquery

    – jose

    26/09/2013 a las 23:59

  • @josh, no estaría de acuerdo con eso… Los métodos de Selenium están destinados a simular interacciones desde el punto de vista de un usuario, mientras que jQuery no lo está. Sí, puede usar ambos para capturar elementos, pero en general debería haber relativamente pocas situaciones en las que necesite ejecutar javascript.

    – wlingke

    16 de diciembre de 2013 a las 15:46


  • El primer fragmento de código asume que jQuery está cargado en la página. El segundo fragmento de código funciona tanto si jQuery está cargado como si no.

    – Luis

    21 de abril de 2016 a las 12:40

avatar de usuario de Selenium no detectado
selenio no detectado

En el HTML que has compartido:

<div id="a">This is some
   <div id="b">text</div>
</div>

El texto This is some está dentro de un nodo de texto. para representar el nodo de texto de forma estructurada:

<div id="a">
    This is some
   <div id="b">text</div>
</div>

Este caso de uso

Para extraer e imprimir el texto This is some desde el nodo de texto usando el cliente python de Selenium, tiene dos formas de la siguiente manera:

  • Usando splitlines(): Puede identificar el elemento principal, es decir <div id="a">extrae el innerHTML y luego usar splitlines() como sigue:

  • usando xpath:

    print(driver.find_element_by_xpath("//div[@id='a']").get_attribute("innerHTML").splitlines()[0])
    
  • usando css_selector:

    print(driver.find_element_by_css_selector("div#a").get_attribute("innerHTML").splitlines()[0])
    
  • Usando execute_script(): También puede utilizar el execute_script() método que puede ejecutar JavaScript sincrónicamente en la ventana/marco actual de la siguiente manera:

  • usando xpath y primer hijo:

    parent_element = driver.find_element_by_xpath("//div[@id='a']")
    print(driver.execute_script('return arguments[0].firstChild.textContent;', parent_element).strip())
    
  • usando xpath y childNodes[n]:

    parent_element = driver.find_element_by_xpath("//div[@id='a']")
    print(driver.execute_script('return arguments[0].childNodes[1].textContent;', parent_element).strip())
    

  • El segundo “usando xpath” es seguido por “find_element_by_css_selector”.

    -Peter Mortensen

    hace 2 días

avatar de usuario de josh
jose

Usar:

def get_true_text(tag):
    children = tag.find_elements_by_xpath('*')
    original_text = tag.text
    for child in children:
        original_text = original_text.replace(child.text, '', 1)
    return original_text

  • esto funciona asquerosamente lento, sin embargo… ¿tiene que haber una mejor manera?

    – jose

    7 sep 2012 a las 21:39

  • Siempre debe intentar obtener el elemento secundario más específico que pueda. En este caso, si tiene muchos elementos secundarios, funcionará lentamente. ¿Por qué no verifica si el elemento realmente tiene texto antes de regresar, es decir, hace el XPath: *[string-length(text()) > 1] o hacer que el bucle for verifique child.text siendo no nulo y no vacío. Además, ¿qué pasa con el selector de CSS? Las consultas XPath son muy lentas de todos modos, por lo que tal vez un selector CSS sea más rápido.

    – Arran

    7 sep 2012 a las 23:53

avatar de usuario de kreativitea
creativitea

No tienes que hacer un reemplazo. Puede obtener la longitud del texto secundario, restarla de la longitud total y dividirla en el texto original. Eso debería ser sustancialmente más rápido.

Avatar de usuario de Pikamander2
Pikamander2

Desafortunadamente, Selenium solo se creó para funcionar con Elementosno Texto nodos.

Si intenta utilizar una función como get_element_by_xpath para apuntar a los nodos de texto, Selenium lanzará un InvalidSelectorException.

Una solución es tomar el HTML relevante con Selenium y luego usar una biblioteca de análisis de HTML como hermosa sopa que puede manejar los nodos de texto con más elegancia.

import bs4
from bs4 import BeautifulSoup

inner_html = driver.find_elements_by_css_selector('#a')[0].get_attribute("innerHTML")
inner_soup = BeautifulSoup(inner_html, 'html.parser')

outer_html = driver.find_elements_by_css_selector('#a')[0].get_attribute("outerHTML")
outer_soup = BeautifulSoup(outer_html, 'html.parser')

A partir de ahí, hay varias formas de buscar el contenido del texto. Tendrá que experimentar para ver qué funciona mejor para su caso de uso.

Aquí hay una sola línea que puede ser suficiente:

inner_soup.find(text=True)

Si eso no funciona, puede recorrer los nodos secundarios del elemento con .contents() y verificar su tipo de objeto.

Hermosa Sopa tiene cuatro tipos de elementosy el que te interesará es el cadena navegable tipo, que es producido por los nodos de texto. Por el contrario, los Elementos tendrán un tipo de Etiqueta.

contents = inner_soup.contents

for bs4_object in contents:

    if (type(bs4_object) == bs4.Tag):
        print("This object is an Element.")

    elif (type(bs4_object) == bs4.NavigableString):
        print("This object is a Text node.")

Tenga en cuenta que Beautiful Soup no admite expresiones XPath. Si los necesita, puede usar algunas de las soluciones en esta pregunta.

¿Ha sido útil esta solución?