Методы .find_element() и find_elements() вы будете использовать всегда при написании парсеров с помощью Selenium. Поэтому их нужно хорошо понимать.
У нас есть страница на сайте с очень простой структурой дерева HTML. На этой странице есть 100 блоков
<div class=»text»>, в каждом три тега <p>, которые не имеют ни class, ни id. Допустим мы хотим собрать каждый первый элемент <p>. Мы могли бы пройти в цикле и использовать срезы, как мы делаем с простыми списками, наверняка подумали вы.
Давайте разбираться, почему срезы не сработают.
.find_element() вернет нам объект веб-элемента, который не поддерживает срезы.
Общий принцип работы:
from selenium import webdriver from selenium.webdriver.common.by import By url = 'https://mob25.com/' with webdriver.Chrome() as browser: browser.get(url) link = browser.find_element(By.CLASS_NAME, 'text') print(type(link))
Вывод:
>>> <class 'selenium.webdriver.remote.webelement.WebElement'>
Мы видим, что возвращаемый тип объекта — это экземпляр класса, который содержит в себе список элементов <p> в количестве трех штук. Но это не простой список.
Давайте посмотрим на возвращаемый объект.
from selenium import webdriver from selenium.webdriver.common.by import By url = 'https://mob25.com/' with webdriver.Chrome() as browser: browser.get(url) link = browser.find_element(By.CLASS_NAME, 'text') print(link)
Вывод:
<selenium.webdriver.remote.webelement.WebElement (session="67761109be77b893aa1625e9d9ddafd4", element="5B0B853EFAAA1FE058CCA69B4278A2CE_element_2")>
Это объект WebElement, который не поддерживает срезы и работу с индексами. Давайте попробуем получить элемент с индексом [1] у этого объекта и посмотрим на результат. Напоминаю, мы пытаемся обратиться к элементу с индексом [1] в теге <div class=»text»>, в котором находятся три тега <p>.
from selenium import webdriver from selenium.webdriver.common.by import By url = 'https://mob25.com/' with webdriver.Chrome() as browser: browser.get(url) link = browser.find_element(By.CLASS_NAME, 'text') print(link[0])
Вывод ошибки:
Traceback (most recent call last): File "E:\Async course\aiohttp\requests-html.py", line 8, in <module> print(link[1]) ~~~~^^^ TypeError: 'WebElement' object is not subscriptable
Эта ошибка говорит о том, что объект типа WebElement не поддерживает индексацию или «срезы». В других словах, мы пытаемся использовать объект WebElement так, как если бы это был список или массив, но Python вам сообщает, что это недопустимо.
Так происходит, потому что все элементы <p>, которые мы храним в этом объекте, являются как бы одним целым. А вот извлечь из этого объекта текст очень просто, достаточно применить к нему метод .text.
from selenium import webdriver from selenium.webdriver.common.by import By url = 'https://mob25.com/' with webdriver.Chrome() as browser: browser.get(url) link = browser.find_element(By.CLASS_NAME, 'text') print(link.text)
.find_element() — Возвращает первый найденный элемент, соответствующий нашим критериям поиска (имеется в виду элемент веб-драйвера, который содержит внутри себя элемент/тег DOM).
.find_elements() — Возвращает все найденные элементы, соответствующие критериям поиска, и сохраняет результат в список <class ‘list’>. Но список будет наполнен не элементами <p>, а элементами веб-драйвера, которые будут содержать в себе элементы DOM.
Как всё-таки быть, если нам нужен каждый второй или третий элемент на странице?
Мы всегда можем решить эту задачу при помощи XPath.
.find_element(By.XPATH, «//div[@class=’text’]/p[2]») — эта команда найдет второй тег <p> внутри первого тега <div> с классом ‘text’, который будет обнаружен на странице первым, и вернет его как объект WebElement.
Давай разберем, что означает каждая часть этого XPath выражения //div[@class=’text’]/p[2].
//: Символы двойного слэша указывают на то, что нужно искать элемент на всей странице, начиная с корневого элемента.
div[@class=’text’]: Эта часть означает, что мы ищем элемент <div>, у которого атрибут class равен ‘text’.
/p[2]: Эта часть указывает, что мы хотим найти второй дочерний элемент <p> внутри ранее найденного <div> с классом ‘text’.
.find_elements(By.XPATH, «//div[@class=’text’]/p[2]») — соответственно, вернёт все найденные элементы <p>, расположенные на вторых позициях, во всех найденных <div class=»text»>.
В самом XPath выражении //div[@class=’text’]/p[2] происходит следующее:
//div[@class=’text’]: Этот фрагмент ищет все <div> элементы с атрибутом class, значение которого равно ‘text’. Здесь двойной слэш // означает, что поиск будет осуществляться по всему дереву DOM, а не только среди дочерних элементов какого-то конкретного элемента.
/p[2]: Этот фрагмент уточняет, что нам нужен именно второй <p> элемент внутри каждого найденного <div> с классом ‘text’.
В итоге, .find_elements(By.XPATH, «//div[@class=’text’]/p[2]») вернет список объектов WebElement, каждый из которых будет представлять второй <p> элемент внутри каждого <div> с классом ‘text’ на странице.
Пример кода:
from selenium import webdriver from selenium.webdriver.common.by import By # URL веб-страницы для парсинга url = 'https://mob25.com/' # Инициализируем драйвер Chrome with webdriver.Chrome() as browser: # Открываем веб-страницу по заданному URL browser.get(url) # Используем метод .find_elements() для поиска всех элементов, соответствующих нашему XPath p_elements = browser.find_elements(By.XPATH, "//div[@class='text']/p[2]") # Проходимся по списку найденных элементов и выводим их текст for i, p_element in enumerate(p_elements): print(f"Текст второго p тега в {i + 1}-м div с классом 'text': {p_element.text}")
Это можно сделать ещё проще с использованием относительного пути XPATH.
first_p = div.find_element(By.XPATH, './p[1]') third_p = div.find_element(By.XPATH, './p[3]')
from selenium import webdriver from selenium.webdriver.common.by import By url = 'https://mob25.com/' # Инициализация драйвера в контексте with, чтобы он закрылся после завершения работы with webdriver.Chrome() as browser: # Открываем URL browser.get(url) # Ищем все div с классом 'text' divs = browser.find_elements(By.CLASS_NAME, 'text') # Проходимся по каждому div for i, div in enumerate(divs): # Получаем первый и третий теги <p> внутри каждого div first_p = div.find_element(By.XPATH, './p[1]') third_p = div.find_element(By.XPATH, './p[3]') # Выводим их текст print(f"Для div #{i+1}, первый p: {first_p.text}, третий p: {third_p.text}")
В этом коде, XPath выражения ./p[1] и ./p[3] относятся к первому и третьему элементу <p> внутри каждого найденного <div> с классом text.
./ — указывает на текущий элемент, в контексте которого происходит поиск. В данном случае, текущим элементом является каждый отдельный <div> с классом text.
p[1] и p[3] — это собственно фильтры, которые указывают, какой именно элемент <p> нам нужен. В XPath, индексация начинается с 1, так что p[1] это первый <p> элемент, а p[3] это третий.
Таким образом, когда вы выполняете div.find_element(By.XPATH, ‘./p[1]’), вы ищете первый тег <p> внутри текущего <div> с классом text. Аналогично, div.find_element(By.XPATH, ‘./p[3]’) находит третий тег <p> внутри этого же <div>.
Эти выражения позволяют вам очень гибко и точно указать, какие именно элементы вам нужны, не прибегая к использованию циклов и сложных срезов.