2025, Nov 01 07:17
Стабильный обход ссылок в Selenium: собираем href, избегаем StaleElementReference
Как стабильно пройтись по списку ссылок в Selenium на Python: соберите href заранее, избегайте StaleElementReferenceException и ошибок WebDriverWait. Пример.
Пробежаться по коллекции WebElement в Selenium, перейти по каждой ссылке и забрать крошку данных — кажется пустяком, пока в игру не вступает навигация. Как только браузер уходит со страницы, ранее найденные элементы теряют актуальность, а наивные стратегии ожидания быстро рассыпаются с ошибками TypeError или StaleElementReferenceException. Ниже — наглядный разбор сбоя и устойчивое решение.
Постановка задачи
Вы начинаете с функции, которая открывает страницу со списком, проходит по карточкам, заходит в ссылку внутри каждой карточки, переходит на страницу детали, достает дату и повторяет цикл. Код выглядит примерно так:
def pull_posting_dates(browser, start_url):
date_bins = []
waiter = WebDriverWait(browser, 5)
browser.get(start_url)
# cards — список веб‑элементов
cards = browser.find_elements(By.CLASS_NAME, 'object-cards-block.d-flex.cursor-pointer')
for card in cards:
try:
anchor = waiter(card, 5).until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, 'div.main-container-margins.width-100 > a')
)
)
except NoSuchElementException:
print('no such element')
# follow the link
browser.get(anchor.get_attribute('href'))
stamp = browser.find_element(
By.XPATH, '//*[@id="Data"]/div/div/div[4]/div[2]/span'
).text
date_bins.append(stamp)
Почему все ломается
Первый затык — TypeError: WebDriverWait is not callable. Передача WebElement в WebDriverWait вроде waiter(card, 5) пытается вызвать объект ожидания как функцию, а это не так. Правильный шаблон: создать WebDriverWait с драйвером и таймаутом, затем вызывать until с ожидаемым условием. Попытка «привязать» ожидание к конкретному элементу таким образом и приводит к ошибке о вызове объекта.
Второй, более принципиальный момент — навигация. Как только вызывается get(), «вид» драйвера на прежнюю страницу исчезает. Selenium хранит ссылки на DOM-узлы в памяти браузера. Переход на новую страницу полностью заменяет DOM, делая накопленные ссылки с прошлого экрана недействительными. Отсюда StaleElementReferenceException после первого перехода и провал повторного использования элементов, собранных до вызова get(). Даже без полной навигации, существенные изменения DOM заставляют искать элементы заново — их позиции в памяти меняются.
Решение: сначала собрать href, затем заходить на каждую страницу
Вариантов несколько, но самый простой — заранее извлечь все атрибуты href, не уходя со страницы. Сохранив ссылки в список Python, можно безопасно итерироваться и вызывать get() для каждой, дожидаясь нужного элемента на странице детали. Подход прямолинейный, надежный и не использует «протухшие» ссылки на элементы. Это не самый быстрый паттерн, зато он прозрачен и работает стабильно.
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
SOURCE_URL = "https://upn.ru/kupit/kvartiry"
def scrape_post_dates(session, entry_url):
session.get(entry_url)
gate = WebDriverWait(session, 10)
predicate = EC.presence_of_all_elements_located
container = (By.CLASS_NAME, "object-cards-block.d-flex.cursor-pointer")
links = []
for node in gate.until(predicate(container)):
a_tag = node.find_element(
By.CSS_SELECTOR, "div.main-container-margins.width-100 > a"
)
href_val = a_tag.get_attribute("href")
if href_val is not None:
links.append(href_val)
stamps = []
for link_url in links:
session.get(link_url)
single_pred = EC.presence_of_element_located
spot = (By.XPATH, "//*[@id='Data']/div/div/div[4]/div[2]/span")
target = gate.until(single_pred(spot))
txt = target.text
stamps.append(txt)
print(txt)
return stamps
if __name__ == "__main__":
opts = ChromeOptions()
opts.add_argument("--headless=new")
with Chrome(options=opts) as drv:
results = scrape_post_dates(drv, SOURCE_URL)
Пример вывода (фрагмент):
Размещено: 16.06.2025 11
Размещено: 06.06.2025 6
Размещено: 02.06.2025 57
Размещено: 19.04.2025 42
Размещено: 03.04.2025 29
Размещено: 25.03.2025 63
...
Почему это важно
Критично понимать, как Selenium отслеживает DOM-узлы. После навигации или заметного обновления DOM старые ссылки теряют силу, потому что структура страницы была заменена. Надежный прием — сначала извлечь примитивные данные вроде URL или ID, затем переходить и заново находить нужное на новой странице. Это делает парсер устойчивым, уменьшает «флапающие» ожидания и локализует отказы.
Если нужна выше пропускная способность, многопоточность даст серьезный прирост, но принцип корректности тот же: никогда не полагайтесь на WebElement между загрузками страниц.
Итоги
Не вызывайте WebDriverWait как функцию и не «прикрепляйте» его к WebElement. Используйте один объект ожидания на контекст драйвера и передавайте условия в until. Не переиспользуйте элементы после навигации; вместо этого соберите href заранее и итерируйтесь по ним, каждый раз находя нужное на целевой странице. С таким паттерном вы избавляетесь от TypeError, обходите «протухшие» ссылки и делаете скрапер предсказуемым. Если позже упремся в производительность, поверх этого подхода можно добавить конкуррентность.