Как использовать 'find_elements_by_xpath' внутри цикла for - PullRequest
0 голосов
/ 29 апреля 2019

Я несколько (или очень) запутался в следующем:

from selenium.webdriver import Chrome
driver = Chrome()

html_content = """
<html>
     <head></head>
     <body>
         <div class='first'>
             Text 1
         </div>
         <div class="second">
             Text 2
                 <span class='third'> Text 3 
                 </span>              
         </div>
         <div class='first'>
             Text 4
         </div>
         <my_tag class="second">
             Text 5
                 <span class='third'> Text 6
                 </span>              
         </my_tag>
     </body>
</html>
"""
driver.get("data:text/html;charset=utf-8,{html_content}".format(html_content=html_content))

Я пытаюсь найти каждый элемент span, используя xpath, распечатать его текст, а затем распечатать текст родительского элемента этого элемента. Окончательный результат должен быть примерно таким:

Text 3
Text 2
Text 6
Text 5

Я могу получить текст span, например:

el = driver.find_elements_by_xpath("*//span")
for i in el:
   print(i.text)

С выводом:

Text 3
Text 6

Но когда я пытаюсь получить текст родителя (и только родителя), используя:

elp = driver.find_elements_by_xpath("*//span/..")
for i in elp:
   print(i.text)

Вывод:

Text 2 Text 3
Text 5 Text 6

Выражения xpath *//span/.. и //span/../text() обычно (но не всегда, в зависимости от того, какой сайт теста xpath используется) оцениваются как:

Text 2
Text 5

, что мне нужно для моего for цикла.

Отсюда путаница. Итак, я думаю, что я ищу, это цикл for, который в псевдокоде выглядит следующим образом:

 el = driver.find_elements_by_xpath("*//span")
 for i in el:
    print(i.text)
    print(i.parent.text) #trying this in real life raises an error....

Ответы [ 4 ]

2 голосов
/ 29 апреля 2019

Я знаю, что уже принял ответ @ JeffC, но в ходе работы над этим вопросом что-то произошло со мной. Скорее всего, это избыточное убийство, но это интересный подход, и я подумал, что ради будущих поколений я мог бы также опубликовать его здесь.

Идея заключается в использовании BeautifulSoup. Причина в том, что у BS есть несколько методов для удаления узлов из дерева. Один из них, который может быть полезен здесь (и для которого, насколько мне известно, у Selenium нет эквивалентного метода), равен decompose() ( см. Подробнее здесь) . Мы можем использовать decompose() для подавления печати второй части text родительского элемента, которая содержится внутри тега span, удаляя тег и его содержимое. Поэтому мы импортируем BS и начинаем с ответа @ JeffC:

from bs4 import BeautifulSoup
elp = driver.find_elements_by_css_selector("span.third")

for i in elp:
    print(i.text)
    s = i.find_element_by_xpath("./..").get_attribute("innerHTML")

и здесь переключитесь на bs4

    content = BeautifulSoup(s, 'html.parser')
    content.find('span').decompose()
    print(content.text)

И вывод, без строковых манипуляций, регулярных выражений или чего-то еще ...:

Text 3   
      Text 2

Text 6
      Text 5
1 голос
/ 29 апреля 2019

Вот метод python, который будет извлекать текст только из родительского узла.

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

Вот как использовать метод в вашем случае:

elements = driver.find_elements_by_css_selector("span.third")
for eleNum in range(len(elements)):
    print(driver.find_element_by_xpath("(//span[@class='third'])[" + str(eleNum+1) +"]").text)
    print(get_text_exclude_children(driver.find_element_by_xpath("(//span[@class='third'])[" + str(eleNum+1) +"]/parent::*")))

Вотвыход: enter image description here

1 голос
/ 29 апреля 2019

i.parent.text не будет работать, в Java я писал что-то вроде

 ele.get(i).findElement("here path to parent may be parent::div ").getText();
0 голосов
/ 29 апреля 2019

Вероятно, есть несколько способов сделать это. Вот один из способов

elp = driver.find_elements_by_css_selector("span.third")
for i in elp:
    print(i.text)
    s = i.find_element_by_xpath("./..").get_attribute("innerHTML")
    print(s.split('<')[0].strip())

Я использовал простой селектор CSS для поиска дочерних элементов («текст 3» и «текст 6»). Я перебираю эти элементы и печатаю их .text, а также перемещаюсь на один уровень вверх, чтобы найти родителя и распечатать его текст. Как отмечено в OP, при печати родительского текста также печатается дочерний текст. Чтобы обойти это, нам нужно получить innerHTML, разделить его и убрать пробелы.

Чтобы объяснить XPath более подробно

./..
^ start at an existing node, the 'i' in 'i.find_element_*'. If you skip/remove this '.', you will start at the top of the DOM instead of at the child element you've already located.
 ^ go up one level, to find the parent
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...