2025, Dec 23 15:01

Стабильный клик по «See all Properties» в Selenium на Magicbricks

Решаем нестабильный клик по «See all Properties» на Magicbricks в Selenium: ждём все экземпляры ссылки, открываем новую вкладку и переключаемся. Надёжно.

Клик по, казалось бы, простой ссылке в Selenium превращается в «движущуюся цель», когда DOM собирается динамически. На magicbricks.com ссылка “See all Properties” появляется рано, но страница проходит промежуточное состояние, в котором эта ссылка ещё не полностью «подключена». Наивная стратегия «подождать и кликнуть» срабатывает лишь изредка и достаточно часто падает, чтобы ломать процессы скрейпинга.

Краткое описание проблемы

Целевая разметка выглядит так и при клике открывает новую вкладку:

<a href="javascript:void(0);" class="mb-home__section__title--anchor-see-all push-right" onclick="fireDynamicPropSeeAllGTM(event,'Owner properties see all','ownerProp');seeAppPropertiesOpenUrl('ownerProp');">
    See all Properties
</a>

Прямолинейная попытка на Selenium ждёт видимости с помощью абсолютного XPath, инициирует клик через JavaScript, затем переключается на новую вкладку:

try:
    ui_wait = WebDriverWait(browser, 10)
    link_see_all = ui_wait.until(
        EC.visibility_of_element_located((By.XPATH, "/html/body/div[5]/div[1]/div[10]/div/section/div[1]/a"))
    )
    base_tab = browser.current_window_handle
    browser.execute_script("arguments[0].click();", link_see_all)
    ui_wait.until(EC.number_of_windows_to_be(2))
    for handle in browser.window_handles:
        if handle != base_tab:
            browser.switch_to.window(handle)
            break
except Exception as err:
    print(f"->Error: {err}")

Несмотря на ожидания, такой подход может приводить к ошибкам драйвера. Ручной клик в браузере работает стабильно и открывает новую вкладку — это намекает, что проблема в тайминге, а не только в локаторах.

Почему клик срабатывает через раз

Страница собирается динамически, и ссылка появляется на промежуточном этапе, когда она ещё не полностью настроена. Базовые ожидания — видимость или даже «элемент кликабелен» — всё ещё могут соревноваться с моментом завершения динамической инициализации. Дополнительная сложность: на странице есть несколько ссылок “See all Properties”. Клик по первой появившейся не гарантирует, что страница готова.

Поэтому минимальное ожидание кликабельности, как ниже, может то сработать, то нет — всё зависит от тайминга:

hold.until(EC.element_to_be_clickable((By.XPATH, "//a[text()='See all Properties']"))).click()

Рабочий подход

Надёжный путь — дождаться появления всех экземпляров целевой ссылки, а уже потом кликать. На этой странице есть три ссылки с точным текстом “See all Properties”. Ожидание, пока их количество не станет равным трём, стабилизирует состояние и делает клик предсказуемым. Затем нужно дождаться второй вкладки, переключиться на неё и продолжить скрейпинг.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
site_url = 'https://www.magicbricks.com/'
browser = webdriver.Chrome()
browser.maximize_window()
browser.get(site_url)
pause = WebDriverWait(browser, 10)
pause.until(lambda d: len(d.find_elements(By.XPATH, "//a[text()='See all Properties']")) == 3)
browser.find_element(By.XPATH, "//a[text()='See all Properties']").click()
pause.until(EC.number_of_windows_to_be(2))
for win in browser.window_handles:
    if win != browser.current_window_handle:
        browser.switch_to.window(win)
        break
for hdr in pause.until(EC.visibility_of_all_elements_located((By.CSS_SELECTOR, "h2")))):
    print(hdr.text)

Этот сценарий кликает по первой подходящей ссылке по видимому тексту после подтверждения, что все три присутствуют. Далее он ждёт появления новой вкладки, переключается на неё и извлекает заголовки.

Почему это важно

Динамические страницы нередко показывают элементы до того, как к ним подвязаны все обработчики или загрузятся зависимые компоненты. Если слишком рано синхронизироваться по одному элементу, возможны разные сценарии: элемент виден, но фактически не кликабелен; другой скрипт вот-вот заменит его; конкурирующий оверлей перехватит клик. Ожидание устойчивого условия, связанного с реальной готовностью страницы, убирает нестабильность.

Ещё несколько практических моментов. Один экземпляр WebDriverWait можно переиспользовать по всему скрипту вместо повторного создания. Запуск без --headless помогает увидеть, что происходит во время гонок. В одних окружениях срабатывает нативный click, в других клик через JavaScript, такой как arguments[0].click(), ведёт себя иначе; например, здесь в Chrome он работает, а у Firefox возникают проблемы. Минимальный рабочий пример облегчает быстрые прогоны и проверку небольших вариаций.

Вывод

Когда страница собирается динамически, синхронизируйтесь с её реальным состоянием готовности, а не с первым видимым совпадением. На magicbricks.com это означает дождаться появления всех трёх ссылок “See all Properties”, кликнуть нужную и переключиться на новую вкладку. Переиспользуйте единый объект ожидания, по возможности отдавайте предпочтение стабильным локаторам по видимому тексту вместо хрупких абсолютных XPath и проверяйте поведение в видимом сеансе браузера перед headless-запусками. Такая комбинация устраняет периодические сбои и даёт надёжный путь к странице объявлений для скрейпинга.