Selenium управляет настоящим браузером. В этом и состоит весь смысл его использования для скрапинга: когда страница формирует содержимое с помощью JavaScript после загрузки начального HTML, простой HTTP-запрос возвращает пустую оболочку, а браузер выполняет скрипты и отдаёт готовый DOM. Расплата за это: теперь для каждой страницы запускается Chrome, что медленнее и тяжелее, чем простое получение HTML. Данное руководство строит рабочий Selenium-скрапер на актуальной основе (Selenium 4), а затем показывает, когда управление браузером собственными силами перестаёт себя оправдывать.

Разрыв версий здесь важен. Большинство учебников по Selenium всё ещё обучают паттернам Selenium 3: find_element_by_id, вручную загружаемые бинарники ChromeDriver, аргументы executable_path. Всё это удалено или объявлено устаревшим. Selenium 4 заменил вспомогательные методы поиска единым API локаторов By, а Selenium Manager (встроенный с версии 4.6) теперь автоматически определяет нужный драйвер для установленного браузера, поэтому шаг загрузки драйвера, с которого открывается большинство руководств, исчез. Всё ниже написано для этой актуальной версии.

Что вам понадобится

Три вещи: Python 3.8 или новее, установленный Google Chrome и пакет Selenium. Это всё. Вам не нужно загружать ChromeDriver и не нужен webdriver-manager при современной установке, потому что Selenium Manager справляется с драйвером самостоятельно.

bash
# Selenium 4.6+ ships Selenium Manager, which resolves
# the matching driver for your installed Chrome.
pip install selenium
В новых проектах webdriver-manager не нужен

Если вы видели webdriver-manager в старых руководствах, он вам больше не нужен. Он решал ту же задачу, что теперь решает Selenium Manager нативно. Оставляйте его только в легаси-коде, который уже от него зависит; для всего нового достаточно установки одного selenium.

Запуск Chrome в headless-режиме

Selenium 4 настраивает браузер через объект Options, передаваемый в драйвер. Для скрапинга почти всегда нужен headless-режим (без видимого окна), а также реальный размер окна и user agent, поскольку некоторые сайты ведут себя иначе, когда окно просмотра крошечное или агент кричит об автоматизации.

python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-gpu")
options.add_argument(
    "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
    "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
)

# No driver path needed. Selenium Manager resolves it.
driver = webdriver.Chrome(options=options)

Если вам всё же нужно зафиксировать конкретный бинарник драйвера, это теперь делается через объект Service (webdriver.Chrome(service=Service("/path/to/chromedriver"), options=options)), а не через удалённый аргумент executable_path. Для большинства пользователей правильный выбор предоставить Selenium Manager выполнять свою работу.

Открытие страницы и поиск элементов

Имея драйвер, driver.get(url) выполняет навигацию и блокирует выполнение до срабатывания начальной загрузки. Затем элементы находятся через API By. Это наиболее существенное изменение по сравнению с Selenium 3: все вспомогательные методы find_element_by_* исчезли, заменённые на find_element(By.X, value).

python
from selenium.webdriver.common.by import By

driver.get("https://quotes.toscrape.com/")

# One element, then many.
title = driver.find_element(By.TAG_NAME, "h1").text
quotes = driver.find_elements(By.CLASS_NAME, "quote")

for q in quotes:
    text = q.find_element(By.CLASS_NAME, "text").text
    author = q.find_element(By.CLASS_NAME, "author").text
    print(author, "-", text)

Наиболее нужные локаторы: By.ID, By.CLASS_NAME, By.CSS_SELECTOR и By.XPATH. CSS-селекторы покрывают большинство потребностей и читаются чисто; XPath используйте только когда нужно сопоставить по текстовому содержимому или перемещаться вверх к родителю, что CSS не умеет. Обратите внимание: find_element выбрасывает исключение, если ничего не найдено, а find_elements возвращает пустой список, поэтому перебирайте форму множественного числа и проверяйте длину, вместо того чтобы оборачивать одиночные поиски в try/except.

Ожидание динамического контента

Вот ошибка, которая ломает большинство начинающих Selenium-скраперов: вызов find_element сразу после возврата get(). На странице с JavaScript-отрисовкой нужный элемент может ещё не существовать, и вы получаете NoSuchElementException на странице, которая нормально загружается при визуальном просмотре. Исправление: явное ожидание, которое опрашивает состояние до выполнения условия или истечения таймаута.

Не тянитесь к time.sleep(). Фиксированный сон либо тратит время, когда страница быстрая, либо даёт сбой, когда она медленная; явное ожидание возвращает результат в момент готовности элемента и истекает только если тот действительно так и не появился.

python
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)

# Wait until at least one .quote is present in the DOM.
wait.until(
    EC.presence_of_element_located((By.CLASS_NAME, "quote"))
)

# Now it is safe to read the rendered content.
quotes = driver.find_elements(By.CLASS_NAME, "quote")

Используйте presence_of_element_located, когда нужно лишь присутствие элемента в DOM, и visibility_of_element_located или element_to_be_clickable, когда вы собираетесь читать текст или кликать. Локатор передаётся в виде кортежа, что легко перепутать: это ((By.CLASS_NAME, "quote")), один аргумент-кортеж, а не два.

Обработка пагинации

Большинство реальных целей распределяют данные по страницам. Паттерн: цикл, в котором вы скрапите текущую страницу, находите элемент управления следующей страницей, кликаете по нему, ждёте нового контента и повторяете, пока элемент не исчезнет. Ловушка: объект страницы устаревает после навигации, поэтому находите кнопку «далее» заново на каждой итерации, а не храните ссылку на неё.

python
from selenium.common.exceptions import NoSuchElementException

all_quotes = []

while True:
    wait.until(
        EC.presence_of_element_located((By.CLASS_NAME, "quote"))
    )
    for q in driver.find_elements(By.CLASS_NAME, "quote"):
        all_quotes.append(q.find_element(By.CLASS_NAME, "text").text)

    try:
        next_btn = driver.find_element(By.CSS_SELECTOR, "li.next a")
    except NoSuchElementException:
        break  # last page reached
    next_btn.click()

print("scraped", len(all_quotes), "quotes")
driver.quit()

Всегда вызывайте driver.quit() по завершении. Он закрывает браузер и процесс драйвера; driver.close() закрывает только текущее окно и оставляет процесс запущенным, что в цикле быстро приводит к утечке экземпляров Chrome. Для страниц с бесконечной прокруткой вместо кнопки «далее» эквивалентом является прокрутка через driver.execute_script("window.scrollTo(0, document.body.scrollHeight)") в цикле с ожиданием, пока количество элементов перестанет расти.

Маршрутизация Selenium через прокси

При скрапинге любой цели в объёме с одного IP вы получите ограничение скорости или блокировку. Маршрутизация браузера через прокси-сервер распределяет запросы по разным адресам, так что ни один не достигает лимита. Простейшая форма передаёт прокси как аргумент Chrome.

python
options = Options()
options.add_argument("--headless=new")
options.add_argument("--proxy-server=http://proxy.example.com:8080")

driver = webdriver.Chrome(options=options)
driver.get("https://httpbin.org/ip")
print(driver.find_element(By.TAG_NAME, "body").text)

Это работает для прокси без аутентификации. Сложность в том, что флаг Chrome --proxy-server не принимает логин и пароль в URL, поэтому прокси с учётными данными требует расширения браузера, внедряющего заголовок аутентификации, или локального ретранслятора, хранящего учётные данные. Это один из неудобных углов прямого управления Chrome. Ротирующий эндпоинт с аутентификацией по токену обходит проблему: вы направляете Selenium на один хост, а ротация и доверие находятся за ним. Компромиссы между управлением собственным пулом и использованием управляемого эндпоинта разбираются в статье backconnect-прокси против crawling API, а если ваши цели хорошо защищены, датацентровые против жилых прокси объясняет, какой тип IP вам действительно нужен.

Когда браузер является неправильным инструментом

Selenium правильный выбор, когда страница действительно требует браузера: тяжёлая клиентская отрисовка, контент, доступный только после взаимодействия, или рабочие процессы, зависящие от кликов и ввода. Он является неправильным инструментом, когда к нему тянутся по привычке. Если данные находятся в начальном HTML, requests плюс парсер на порядок быстрее и легче. А как только цель начинает активно противодействовать с помощью anti-bot-защиты, сырой headless-браузер всё равно получает отпечаток и блокируется, и вы в итоге вручную перестраиваете ротацию, повторные попытки и маскировку.

Approach Renders JS Скорость / стоимость Best for
requests + парсер No Fastest, lightest Данные в исходном HTML, без JS
Selenium Да (реальный браузер) Slow, heavy Рендеринг JS, клики, формы, авторизация
Управляемый crawling API Yes (server-side) Один запрос, без инфраструктуры Защищённые цели, масштаб, без парка браузеров

Третья строка описывает место большинства боевых скраперов. Управляемый crawling API отрисовывает страницу на стороне сервера, ротирует IP, повторяет запрос при блокировках и возвращает готовый HTML из одного запроса, поэтому вы никогда не запускаете и не закаляете собственный парвк браузеров от отпечатков. Selenium оставляйте для реальных взаимодействий, а высокообъёмную работу, которая блокируется, делегируйте API.

Crawlbase Crawling API

Когда страница требует JavaScript, но вы предпочитаете не запускать парк браузеров, Crawling API отрисовывает её на стороне сервера, ротирует IP и повторяет запрос при блокировках, возвращая готовый HTML из одного запроса. Отправьте URL с &javascript=true и полностью избегите headless-инфраструктуры. Опробуйте на бесплатном уровне против вашей реальной цели.

Вызов представляет собой один HTTP-запрос: нет драйвера, нет явных ожиданий, нет танцев с аутентификацией прокси. Просто отправьте токен и целевой URL, запросите рендеринг и считайте тело ответа.

python
import requests

resp = requests.get(
    "https://api.crawlbase.com/",
    params={
        "token": "_YOUR_TOKEN_",
        "url": "https://quotes.toscrape.com/js/",
        "javascript": "true",  # render the page server-side
    },
)
print(resp.status_code)
print(resp.text[:500])

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

Итоги

Ключевые выводы

  • Используйте паттерны Selenium 4. API локаторов By заменил все вспомогательные методы find_element_by_*, а Selenium Manager разрешает драйвер, поэтому шаг ручной загрузки исчез.
  • Настраивайте браузер через Options. Headless-режим, реальный размер окна и user agent являются базовыми параметрами для скрапинга.
  • Ждите явно, никогда не спите. WebDriverWait в сочетании с expected_conditions возвращает результат в момент готовности контента и является исправлением для гонок при JS-отрисовке.
  • Повторно ищите элементы после навигации. Ссылки устаревают после смены страниц; заново запрашивайте кнопку следующей страницы в каждом цикле и вызывайте driver.quit() в конце.
  • Браузер не всегда является ответом. Используйте requests, когда данные находятся в HTML, и управляемый crawling API, когда цели хорошо защищены или нужен масштаб без парка браузеров.

Часто задаваемые вопросы

Нужно ли загружать ChromeDriver для Selenium 4?

Нет. Начиная с Selenium 4.6, встроенный Selenium Manager определяет установленный Chrome и автоматически загружает соответствующий драйвер, поэтому достаточно простой команды pip install selenium. Путь к драйверу указывается через объект Service только когда нужно намеренно зафиксировать конкретный бинарник; старый аргумент executable_path удалён.

Почему find_element выбрасывает NoSuchElementException на странице, которая нормально загружается?

Потому что элемент отрисовывается JavaScript после начальной загрузки, а ваш код запросил DOM раньше, чем он появился. Замените немедленный поиск явным ожиданием: WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, "quote"))). Ожидание опрашивает состояние до появления элемента или истечения таймаута, что устраняет как фиксированные задержки, так и гонки состояний.

Что лучше использовать для веб-скрапинга: Selenium или BeautifulSoup?

Используйте requests с парсером вроде BeautifulSoup, когда данные присутствуют в начальном HTML страницы; это несравнимо быстрее и легче, поскольку браузер не запускается. Используйте Selenium, когда контент отрисовывается JavaScript или нужно кликать, вводить текст, прокручивать или входить в систему. Многие скраперы комбинируют оба подхода: Selenium отрисовывает страницу, затем BeautifulSoup разбирает полученный HTML.

Как использовать аутентифицированный прокси с Selenium?

Аргумент Chrome --proxy-server не принимает логин и пароль в URL, поэтому прокси с учётными данными требует расширения браузера, внедряющего заголовок аутентификации, или локального ретранслятора, хранящего учётные данные. Ротирующий эндпоинт с аутентификацией по токену решает проблему: вы направляете Selenium на один хост, а учётные данные хранятся за ним, а не в флагах запуска.

Хорошо ли Selenium подходит для масштабного скрапинга?

Масштабируется он плохо. Каждая страница запускает полноценный браузер, что медленно и требовательно к памяти, а на хорошо защищённых целях сырой headless-браузер всё равно получает отпечаток и блокируется. Для большого объёма управляемый crawling API с серверной отрисовкой, ротацией IP и повторными попытками при блокировках обычно является лучшим выбором, оставляя Selenium для реальных взаимодействий.

В чём разница между driver.close() и driver.quit()?

Метод driver.close() закрывает текущее окно браузера, но оставляет процесс драйвера и все другие окна запущенными. Метод driver.quit() закрывает каждое окно и завершает процесс драйвера. В цикле скрапинга всегда завершайте работу вызовом driver.quit(), иначе осиротевшие процессы Chrome накапливаются и исчерпывают память.

Начать создавать

Обходите любой сайт в масштабе, без борьбы с инфраструктурой.

Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

Самообслуживание · Звонок отдела продаж не требуется · Доступны корпоративные объёмы краулинга