Зачем нужны селекторы
После долгого использования метода .find() ваш код рано или поздно превращается в кашу. Например, вот код парсера для интернет-магазина:
product_cards = soup.find('main').find('table', class_='MainTable').find_all('div', class_='product-card')
for product_card in product_cards:
product_image = product_card.find('img')
...
Код ищет карточки товаров по сложной комбинации из тегов и классов, потом в цикле снова начинаем погружаться в теги и классы, чтобы достать картинки товаров. Такой код совершенно нечитаемый и неудобный, без дополнительных комментариев невозможно понять, что тут происходит.
Это можно исправить, если использовать селекторы.
Кто такие селекторы
Селекторы — это правила, по которым можно найти элемент в HTML. По-сути, они и написаны в коде выше, только с помощью методов .find(). Например, возьмём код с несколькими .find():
soup.find('main') \
.find('table', class_='MainTable') \
.find_all('div', class_='product-card')
Этот код в виде селектора выглядит так:
main table.MainTable div.product-card
Чтобы создать селектор, достаточно перечислить теги, так же, как в .find(). Если вы использовали поиск по классам, то они пишутся через точку после тега.
А вот как использовать селектор:
selector = "main table.MainTable div.product-card"
product_cards = soup.select(selector)
Код стал заметно чище и проще. Сравните:
product_cards = soup.find('main').find('table', class_='MainTable').find_all('div', class_='product-card')
for product_card in product_cards:
product_image = product_card.find('img')
# vs
selector = "main table.MainTable div.product-card"
product_cards = soup.select(selector)
for product_card in product_cards:
product_image = product_card.find('img')
Но это ещё не всё. Селекторы сильнее, чем комбинация нескольких .find(). Например, чтобы выбрать все картинки из карточек товаров, достаточно добавить к селектору img. Т.е. теперь можно ещё и от цикла избавиться:
product_cards = soup.find('main').find('table', class_='MainTable').find_all('div', class_='product-card')
for product_card in product_cards:
product_image = product_card.find('img')
# vs
images_selector = "main table.MainTable div.product-card img"
product_images = soup.select(images_selector)
Метод .select() сам возьмёт все карточки книг и найдёт в них все картинки, которые есть внутри. Больше для этого не нужно писать циклы.
Более того, можно сделать селекторы ещё и стабильнее. Вёрстка сайта может измениться: один <div> заменили на <span>, другой <div> стал семантичным <article>, третий <div.container> совсем исчез. Если перечислить все теги на пути от <body> до самого нижнего <img>, то любое изменение в верстке убъет парсер. Нам придётся его чинить, его работа будет неустойчивой.
Теги могут заменять один на другой, а классы обычно оставляют на местах. Можно убрать из селектора теги, к которым привязаны классы:
main table.MainTable div.product-card img
main .MainTable .product-card img
Эти два селектора сработают одинаково.
.select_one()
Метод .find_all() возвращает все подходящие элементы, так же, как и метод .select().
У него есть брат-близнец, который возвращает только один, первый подходящий элемент: .find(). У .select() тоже есть такой брат-близнец: .select_one(). Он возвращает только первый найденный по селектору элемент.