перейти к содержанию

Как дождаться загрузки страницы в Selenium? Руководство эксперта

Дайте угадаю: вы начали очищать сайт с помощью Selenium и внезапно столкнулись с ужасными ошибками тайм-аута, устаревшими исключениями элементов и ненадежными локаторами. Звучит знакомо?

Многие из нас были там! В современной динамичной сети правильное ожидание полной загрузки страниц перед взаимодействием имеет решающее значение для надежной автоматизации.

В этом подробном руководстве, состоящем из более чем 3200 слов, я воспользуюсь своим более чем 5-летним опытом работы в качестве профессионального эксперта по парсингу веб-страниц, чтобы изучить различные методы и лучшие практики корректного ожидания в Selenium.

Независимо от того, новичок вы или опытный профессионал, надежная логика ожидания является обязательным инструментом для обеспечения стабильности. Давайте погрузимся!

Почему нельзя просто спешить

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

Но сегодняшняя сеть очень динамична. В соответствии с Исследование Google, среднее время первого рисования составляет 1.7 с, но среднее время полного интерактивного взаимодействия является колоссальным. 15 секунд. Это много времени для загрузки контента.

Если вы, как скребок, торопитесь, то можете столкнуться с некоторыми распространенными проблемами:

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

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

В цифрах: время загрузки страницы

Чтобы понять, как долго нам, возможно, придется ждать, давайте посмотрим на некоторые реальные показатели производительности загрузки страниц из Отчет о состоянии Интернета за 2020 год от Акамай:

  • Среднее время взаимодействия: 15s
  • Средний вес страницы: 2744KB
  • Среднее количество запросов: 105
  • Среднее количество изображений на странице: 53
  • Байтов JavaScript на страницу: 453KB

Сегодня страницы стали больше и сложнее, и после первоначального ответа выполняется гораздо больше работы. Для скраперов крайне важно дождаться интерактивности, а не только первой отрисовки.

Распространенные исключения, вызванные отсутствием ожидания

Вот некоторые конкретные исключения, которые могут возникнуть, когда элементы еще не готовы:

  • StaleElementReferenceException – Элемент удален из DOM после выборки.
  • Элементнотинтерактаблеисключение – Попытка щелкнуть невидимый элемент
  • NoSuchElementException – Тайм-аут поиска истек, поскольку элемент еще не существует

Каждое из этих значений указывает на то, что скребку требуется больше ожидания.

Явное ожидание — ваш друг

Чтобы избежать этих ошибок, нам нужно дождаться полной визуализации страницы, прежде чем взаимодействовать. В Selenium есть два основных подхода:

Неявные ожидания – Установите глобальное время ожидания для драйвера.

Явное ожидание – Подождите, пока возникнут определенные условия.

В большинстве случаев явное ожидание предпочтительнее неявного. Давайте разберемся, почему.

Неявное ожидание: подход «Кувалды»

Неявное ожидание устанавливает тайм-аут драйвера для опроса DOM при поиске элементов. Это означает, что каждый раз, когда вы звоните:

driver.find_element_by_id(‘some-id‘)

Драйвер будет повторять попытку в течение неявного периода ожидания, чтобы найти этот элемент, прежде чем выдать исключение NoSuchElementException.

Вы можете использовать его так:

driver = webdriver.Chrome()
driver.implicitly_wait(10) 

Теперь все поиски будут повторяться в течение 10 секунд, чтобы найти элементы, если они не присутствуют сразу.

Обратной стороной является то, что он ожидает каждого локатора, даже тех, которые не нужны для определения готовности страницы. Это может сильно замедлить работу вашего скребка.

Думайте о неявном ожидании как о добавлении 5-секундного ожидания к каждой выборке элемента. Это складывается!

Точность явного ожидания

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

Ключевые идеи:

  • Ждите только тогда, когда это необходимо – Избегайте ненужных ожиданий, не связанных с готовностью страницы.
  • Точные условия – Ждите точных элементов или состояний, а не просто общего времени.
  • Трансформируемость – Настройте логику ожидания на странице с различными условиями.
  • удобочитаемый – Легко понять намерения при повторном просмотре старого кода.

Вот типичный пример ожидания появления элемента:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait 

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "myDynamicElement"))
)

Это приостанавливает выполнение до тех пор, пока не загрузится элемент с идентификатором «myDynamicElement» или не пройдет 10 секунд.

Другие полезные ожидаемые условия предоставлено Selenium следующие:

  • title_contains() – Подождите, пока заголовок страницы обновится.
  • staleness_of() – Подождите, пока элемент больше не будет прикреплен к DOM.
  • element_to_be_clickable() – Подождите, пока элемент станет видимым и включенным

Явное превосходит неявное: пример из реальной жизни

Давайте сравним два ожидания на реальном примере.

Допустим, я сканирую сайт, на котором есть панель навигации, левая боковая панель и основной контент.

Ключевой элемент, который мне нужно дождаться, — это идентификатор «#main-content», в котором отображаются мои данные.

С неявным ожиданием:

  • К каждому поиску элемента добавляется 10 секунд, даже если это не требуется.
  • Все еще склонен к ошибкам устаревших элементов, если слишком быстро

С явным ожиданием:

  • Ждите только тогда, когда это необходимо для селектора #main-content
  • Избегайте ненужного ожидания навигации и боковой панели.
  • Прежде чем продолжить, дождитесь загрузки данных.

Выборочно ожидая одного состояния готовности, например элемента, я избегаю ненужных задержек.

Шаблоны эффективного явного ожидания

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

Ожидание загрузки страницы

Ожидание готовности документа — распространенный метод определения завершения загрузки:

WebDriverWait(driver, 10).until(
   lambda d: d.execute_script(‘return document.readyState‘) == ‘complete‘
)

При этом браузер опрашивается до тех пор, пока состояние готовности не станет «завершенным», что указывает на то, что все ресурсы загружены.

Более легкий шаблон отслеживает конкретные элементы высокого уровня:

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "main-content"))
) 

Это удается, когда загружается основной раздел контента, не дожидаясь всего остального.

Ожидание каждого действия

Вы также можете подождать непосредственно перед тем, как совершить какое-либо действие, например, щелкнуть элемент:

menu = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "top-menu"))
)

submenu = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.ID, "submenu"))
)

submenu.click()

Это гарантирует, что и верхнее меню, и подменю будут готовы перед нажатием.

Параллельное ожидание

Ожидание нескольких условий может подтвердить готовность страницы:

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "header")),
    EC.presence_of_element_located((By.ID, "footer")), 
    EC.presence_of_element_located((By.ID, "main"))
)

Требование загрузки верхнего, нижнего колонтитула и основного контента снижает количество ложных срабатываний.

Связанные и вложенные ожидания

Для расширенных сценариев вы также можете вкладывать ожидания:

element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "dropdown"))
)

menu = WebDriverWait(element, 10).until(
    EC.presence_of_element_located((By.ID, "menu"))  
)

Сначала он ожидает родительский элемент, а затем дочерний элемент внутри него.

Ожидание опроса AJAX

Некоторые сайты загружаются посредством непрерывных запросов AJAX. Вы можете зациклить ожидание изменений:

while True:

    current_count = driver.find_element_by_id(‘result-count‘).text

    # If count changed since last check, page is still loading
    if current_count != previous_count:
        previous_count = current_count
        continue 

    break # Page loaded!

Это опрашивает элемент в поисках изменений для обнаружения загрузки.

Асинхронное ожидание

В асинхронных фреймворках, таких как asyncio, вы можете ждать промисов:

await page.waitForSelector(‘#content‘)

Синтаксис немного отличается, но обеспечивает асинхронное ожидание.

Неявная + явная комбинация

Вы даже можете комбинировать неявное и явное ожидание:

driver.implicitly_wait(10) 

my_element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "my-element"))
)

Таким образом, у вас есть как глобальное, так и конкретное ожидание. Просто убедитесь, что они используют разумную продолжительность.

Выбор локаторов, сигнализирующих о готовности

При выборе локаторов для ожидания вам нужны элементы, соответствующие этим критериям:

  • Появляются поздно в процессе загрузки
  • Иметь уникальные идентификаторы или классы, которые не изменятся.
  • Расположен над сгибом для быстрой проверки
  • Маловероятно, что они будут перемещены из-за изменений на сайте.
  • Не удаляйтесь из DOM и не устаревайте.

Вот несколько распространенных примеров:

  • Основной заголовок или навигация загружаются после ресурсов
  • Контейнеры или виджеты основного контента
  • Нижний колонтитул страницы
  • Небольшие динамические элементы пользовательского интерфейса, такие как кнопки.

Индикаторы загрузки, такие как счетчики, также являются отличными триггерами ожидания, когда они исчезают.

Настройка таймаутов для оптимального ожидания

Установка слишком больших таймаутов может действительно замедлить работу парсера, но слишком короткие могут привести к нестабильным сбоям.

Вот несколько рекомендаций по настройке длительности:

  • Установите таймауты загрузки страниц дольше, примерно на 10-20 секунд.
  • Используйте более короткие таймауты, например 3-5 секунд, для отдельных элементов.
  • Учитывайте производительность браузера на мобильных устройствах и на настольных компьютерах.
  • Фактор задержки в сети (широкополосная связь или 3G).
  • Отслеживайте ошибки тайм-аута и при необходимости увеличивайте значение.
  • Проанализируйте водопад загрузки страницы и определите типичное время загрузки.
  • Запланируйте дополнительные 1–2 секунды в качестве буфера.
  • Стандартизируйте аналогичные ожидания в вашей кодовой базе.

По мере того, как вы парсите больше страниц, вы получаете лучшее представление об оптимальном ожидании для надежности.

Обработка сбоев ожидания и тайм-аута

Даже при длительном ожидании время от времени все равно могут возникать тайм-ауты. Вот несколько способов справиться с ними:

  • Подробности отладки журнала – Добавление отпечатков помогает определить, где ожидание терпит неудачу.
  • Повторить попытку по истечении времени ожидания – В случае неудачи повторите короткие явные ожидания до 3 раз.
  • Увеличить таймаут – Если происходит много таймаутов, постепенно увеличивайте время ожидания.
  • Используйте попытку/исключение – Перехватывайте определенные исключения, такие как StaleElementReference.
  • Отключить при сбое – Вы можете пропустить ожидание после повторных сбоев, чтобы позволить тестам продолжиться.

Благодаря некоторой встроенной устойчивости эти спорадические проблемы не сломают ваш парсер.

Ожидание на других языках

До сих пор примеры были на Python, но явные ожидания доступны во всех языках:

  • JavaWebDriverWait и ExpectedConditions
  • C#WebDriverWait и ExpectedConditions
  • RubyWebDriver::Wait и ExpectedConditions
  • JavaScriptbrowser.wait() и служебные методы

Концепции очень похожи – лишь немного отличается синтаксис.

За пределами Selenium: больше инструментов ожидания

Помимо Selenium, есть еще несколько полезных библиотек ожидания:

  • Времяtime.sleep() прост, но приостанавливает все выполнение.
  • Повторите - Повторить пакет делает повторные попытки и легко ждет.
  • Айоhttpawait response.text() ожидает завершения сетевых вызовов.
  • Красивый супBeautifulSoup(page.content, features="lxml") будем ждать полного разбора.
  • Scrapyyield scrapy.Request(url, callback=self.parse) является асинхронным.

Их сочетание с Selenium обеспечивает надежное ожидание в вашем коде.

Вкратце: подождите и утилизируйте надежно

В заключение отметим пять ключевых выводов:

  1. Используйте явные ожидания – Они избегают ненужных тайм-аутов и нацелены на конкретные условия.

  2. Ждите нескольких сигналов – Объедините ожидания заголовка, тела, нижнего колонтитула и т. д., чтобы подтвердить готовность страницы.

  3. Настраивайте таймауты с умом – Установите значения на основе реальных данных о загрузке страниц, чтобы оптимизировать задержки.

  4. Стандартизируйте ожидания – Повторно используйте согласованные шаблоны в своей базе кода.

  5. Добавьте устойчивости – Реализуйте повторные попытки и обработку сбоев для учета динамических страниц.

Поначалу ожидание может показаться утомительным. Но инвестиции в надежную логику ожидания вознаградят вас надежными и отказоустойчивыми парсерами, подготовленными для современной сети.

Надеюсь, эти шаблоны и советы, извлеченные из моего опыта работы профессиональным специалистом по парсингу веб-страниц, помогут вам успешно ждать. Бросайте!

Теги:

Присоединяйтесь к беседе

Ваш электронный адрес не будет опубликован. Обязательные поля помечены * *