В статье разберем, как эффективно реализовать алгоритм парсинга на Python с Selenium, какие проблемы могут возникать и как их обходить. Также затронем вопросы оптимизации кода, конфигурации сервера и нюансы сохранения полученных данных.
Напоминаем, что получение данных в автоматическом режиме может быть запрещено условиями использования веб-сайтов. Мы не поощряем нарушения этих условий или иных правовых норм.
Большинство современных сайтов не отдают данные напрямую в виде статической HTML разметки. Страницы формируются «на лету» с использованием JavaScript.
Для поиска источника нужных данных можно попробовать найти запрос, который совершается через JavaScript на странице. Обычно такие запросы возвращают ответ в формате JSON или HTML. Подробнее об этом мы говорили в предыдущей статье.
В остальных случаях на помощь приходит Selenium — это библиотека для автоматизации веб-браузера. Она позволяет программно управлять браузером: эмулировать действия реального пользователя в браузере, что дает возможность загрузить динамический контент и извлечь из него нужные данные.
Динамическая подгрузка данных. Динамически подгружаемые данные иногда сложно отследить через веб-инспектор во вкладке запросов (например, из-за шифрования данных).
Использование WebSockets. Некоторые сервисы передают важную информацию через постоянное соединение по WebSockets. Стандартные методы не могут перехватить эти данные.
Сложные механизмы валидации. Когда нужно не только загрузить страницу, но и выполнить авторизацию или кликнуть на определенные элементы, без полноценного браузера не обойтись.
В таких случаях Selenium часто становится единственным решением.
Selenium всегда используется как запасной вариант, когда никакие другие методы не помогают. Это связано с тем, что библиотека запускает полноценный браузер — это расходует больше времени и ресурсов.
Рассмотрим, в каких случаях использование эмуляции браузера будет оправдано:
Сложная навигация. Если контент появляется только после серии кликов или скроллов.
Интерактивность. Необходимо выполнить вход в личный кабинет, ввести CAPTCHA (или обойти ее), заполнить форму и так далее.
Точность. Если нужно получить данные в той же форме, в которой их видит реальный пользователь — чтобы не собирать данные частями по запросам во вкладке Network.
Также предпочтительны сценарии, когда Selenium используется только для авторизации и последующего получения куки-файлов. Сами запросы в таком случае выполняются без эмуляции браузера, через библиотеку requests. Это значительно сократит потребление ресурсов.
cloud
Для начала работы нужно понимать базовый набор инструментов и библиотек, которые позволят эмулировать браузер.
Главная составляющая Selenium — это WebDriver, который взаимодействует с движком конкретного браузера (Chrome, Firefox, Edge и другими).
Принцип работы следующий:
Ваш скрипт на Python отправляет команды WebDriver (вроде «открой URL», «нажми на элемент» и т.д.).
WebDriver передает эти команды соответствующему драйверу (например, chromedriver
).
Драйвер уже напрямую управляет браузером, совершая запрашиваемые действия.
Примеры в статье разобраны на облачном сервере Timeweb Cloud фиксированной конфигурации CPU 2 x 3.3 ГГц, RAM 2 ГБ, NVMe 40 ГБ, ОС Ubuntu 22.04. Используется Python 3.10. Код включает в себя использование Google Chrome, при необходимости можете использовать другой драйвер.
Перед началом проверьте, что используемый браузер установлен в систему. Например:
google-chrome --version
Если браузера нет в системе, его нужно установить. Для установки Google Chrome на Ubuntu используйте:
sudo apt update
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb -y
Рекомендуем создать и активировать виртуальное окружение. Зачастую проекты, связанные с парсингом, впоследствии разворачиваются на серверах. Поэтому использование виртуального окружения поможет без труда согласовать версии нужных библиотек.
Далее установите Selenium через pip:
pip install selenium
Подключите Selenium в своем скрипте и проверьте работоспособность:
from selenium import webdriver
options = webdriver.ChromeOptions()
# Раскомментируйте следующие строки, если используете сервер без GUI и получаете ошибку “session not created: probably user data directory is already in use, please specify a unique value for --user-data-dir argument, or don't use --user-data-dir”
# options.add_argument("--headless")
# options.add_argument("--no-sandbox")
# options.add_argument("--disable-dev-shm-usage")
# Создаем объект браузера (в данном случае Chrome)
driver = webdriver.Chrome(options=options)
# Открываем страницу
driver.get("https://timeweb.cloud")
# Получаем заголовок
print(driver.title)
# Завершаем работу
driver.quit()
С выпуском Selenium 4.6.0 появился встроенный механизм Selenium Manager, который автоматически загружает подходящую версию драйвера. Однако версии браузера и драйвера могут не совпадать, даже при использовании Selenium Manager, и в таком случае потребуется ручная установка.
На этом базовая настройка завершена. Далее можно перейти к настройке Chromedriver, выбору режима headless и другим расширенным возможностям Selenium Python.
Chromedriver — это отдельное приложение (исполняемый файл), которое нужно скачать с официального сайта. Важно, чтобы версия Chromedriver соответствовала версии Chrome (или Chromium) на вашей системе. Если версии не совпадают, будут возникать проблемы совместимости.
Для установки Chromedriver на Ubuntu можно использовать менеджер пакетов:
apt-get install chromium-chromedriver
Пользователи на Windows могут скачать chromedriver.exe
с официального сайта.
Шаги для согласования Chromedriver и Chromium между собой:
Установить Chrome или Chromium.
Скачать Chromedriver той же версии.
Указать путь к Chromedriver в переменной окружения или прописать в коде:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
service = Service('/usr/bin/chromedriver')
options = webdriver.ChromeOptions()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
# Создаем объект браузера (в данном случае Chrome)
driver = webdriver.Chrome(service=service, options=options)
# Открываем страницу
driver.get("https://timeweb.cloud")
# Получаем заголовок
print(driver.title)
# Завершаем работу
driver.quit()
Selenium предоставляет удобный интерфейс для управления браузером. Рассмотрим основные функции, использование которых приведет к решению большинства возможных задач по парсингу и автоматизации.
Метод .get(url)
загружает нужный сайт:
driver.get("https://example.com")
Selenium позволяет находить элементы по разным критериям.
from selenium.webdriver.common.by import By
element = driver.find_element(By.TAG_NAME, "h1") # Поиск первого <h1>
elements = driver.find_elements(By.CLASS_NAME, "product") # Поиск всех элементов с классом
Часто нужно получить текст элемента или ссылку.
print(element.text) # Получение текста
print(element.get_attribute("href")) # Получение ссылки
Selenium может кликать и вводить текст.
button = driver.find_element(By.ID, "submit")
button.click() # Клик по кнопке
input_field = driver.find_element(By.NAME, "q")
input_field.send_keys("Selenium") # Ввод текста
Иногда нужно выполнять JS-код вручную. Это используется для прокрутки страницы или обхода защиты от ботов.
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
Некоторые сайты загружают контент с задержкой, поэтому используется WebDriverWait
.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
element = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "price")))
Некоторые сайты открывают данные в новых вкладках. Следующая функция используется для работы с несколькими окнами.
tabs = driver.window_handles
driver.switch_to.window(tabs[1]) # Переключение на вторую вкладку
Один из наиболее популярных режимов — headless
, то есть без отображения графического интерфейса. Этот режим полезен:
На серверах без установленного окружения рабочего стола (X11, Wayland).
Для снижения нагрузки: отсутствие отрисовки интерфейса значительно экономит ресурсы.
Пример запуска в headless-режиме:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
Однако у headless-браузеров есть и недостатки, главные из которых — повышенная вероятность детектирования со стороны сайтов и некоторые проблемы с рендерингом динамических элементов.
Многие сайты пытаются защититься от автоматизированных ботов, анализируя поведение пользователя и сигнатуры браузера. При запуске в headless-режиме Chrome сообщает о себе некоторыми особыми значениями. Эти данные могут сигнализировать сайту, что перед ним не реальный пользователь.
Если вы встретились с подобной проблемой, то это повод ознакомиться с условиями использования сайта-донора. Нужно остановить работу, если сбор данных нарушает правила.
При автоматизации тестирования веб-приложений с Selenium в Python иногда возникает необходимость детального анализа страниц, выполняемого JavaScript и сетевых запросов. Конечно, при отладке алгоритма на локальной машине вы можете не использовать headless
режим и использовать окно браузера, с которым работает Selenium. Но для отладки программ в headless-режиме пригодится удаленная отладка (remote debugging).
Режим удаленной отладки позволяет подключаться к браузеру через DevTools и следить за происходящими процессами. Такой подход особенно полезен при нестабильной загрузке элементов, неожиданных редиректах и сложных сценариях, где стандартных логов недостаточно.
Для активации пропишите следующую опцию перед запуском драйвера:
chrome_options.add_argument("--remote-debugging-port=9222")
В Google Chrome перейдите на страницу chrome://inspect
. Когда скрипт с Selenium запущен на стандартном порте 9222
, Вы увидите доступный драйвер для подключения. Нажмите кнопку «inspect».
Вы увидите стандартный интерфейс DevTools в Google Chrome и экран, с которым в данный момент взаимодействует Selenium.
Когда объем данных большой, а парсинг запускается регулярно, важно правильно подобрать аппаратные ресурсы и ПО.
Требования к серверу зависят от:
Одновременного количества потоков. Если вы параллельно поднимаете десятки инстансов браузера, требуется больше ядер CPU и памяти.
Типа данных. Некоторые сайты тяжеловесны, содержат много JavaScript; браузеру нужно больше времени и ресурсов, чтобы все отрисовать.
Частоты обновления. Если парсинг запускается раз в день — можно обойтись дешевым VPS. Если же нужен непрерывный сбор данных, придется подумать о более производительных решениях.
Для расчета конфигурации можно использовать следующие параметры:
Оперативная память (RAM): каждый браузер в среднем потребляет от 200 до 500 МБ. На десятки параллельных запущенных браузеров нужно уже несколько гигабайт.
CPU: минимум 2–4 ядра. Если нужно масштабироваться, имеет смысл взять 8+ ядер, чтобы распределить нагрузку.
Диск (SSD): у Selenium большие требования к скорости чтения-записи, особенно если есть логирование и временные файлы.
Облачная инфраструктура Timeweb Cloud предоставляет возможность создать облачный VPS-сервер с рекомендуемой конфигурацией (CPU 2 x 3.3 ГГц, RAM 2 ГБ, NVMe 40 ГБ) стоимостью 850 рублей в месяц. Можно попробовать использовать сервера с конфигурацией ниже, но стабильность работы Selenium в этом случае не будет гарантирована.
Следующие параметры помогают избежать ошибок и ограничений при запуске браузера.
Настройка |
Что она делает? |
Почему это важно? |
|
Отключает аппаратное ускорение через GPU. |
Полезно при запуске в headless-режиме, так как без GPU могут возникать ошибки рендеринга. |
|
Использует |
При запуске в контейнерах Docker стандартный |
|
Отключает изолированную среду (sandbox) Chrome. |
Позволяет запускать браузер без ошибок в контейнерах и виртуальных машинах. |
Воспользуйтесь следующими советами, чтобы обходить антибот системы:
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
Что делает? Отключает флаг enable-automation
, который указывает, что браузер запущен через Selenium.
Почему важно? Многие сайты проверяют этот флаг через navigator.webdriver
и могут сразу заблокировать пользователя.
chrome_options.add_experimental_option("useAutomationExtension", False)
Что делает? Отключает автоматическое расширение Selenium Automation Extension, которое браузер добавляет при запуске.
Почему важно? Некоторые сайты проверяют загруженные расширения и могут заметить Selenium.
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
Что делает? Отключает специальную функцию Blink (рендеринг-движок Chrome), которая помечает браузер как управляемый автоматизированными инструментами.
Почему важно? Некоторые сайты используют navigator.webdriver
для определения автоматизированного доступа.
chrome_options.add_argument("--disable-webrtc")
Что делает? Отключает WebRTC, который может раскрыть реальный IP-адрес пользователя.
Почему важно? Некоторые сайты отслеживают WebRTC-запросы, чтобы обнаружить подмену IP-адреса.
Если сайт блокирует ваш IP, стоит проксировать запросы через разные серверы.
Для подключения прокси без авторизации нужно добавить дополнительный аргумент.
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
proxy = "123.45.67.89:8080" # IP:PORT прокси
chrome_options = Options()
chrome_options.add_argument(f"--proxy-server={proxy}")
driver = webdriver.Chrome(service=Service("chromedriver"), options=chrome_options)
driver.get("https://www.whatismyipaddress.com/")
Для прокси с авторизацией процесс подключения чуть сложнее. Тут поможет Selenium Wire — расширение для стандартного Selenium, которое позволяет работать с HTTP/HTTPS-запросами во время работы веб-драйвера.
Установите библиотеку через pip:
pip install selenium-wire
Подключить такой вид прокси можно с помощью следующего примера:
import time
from seleniumwire import webdriver
proxy_host = "123.45.67.89"
proxy_port = "8080"
proxy_user = "user"
proxy_pass = "password"
options = {
'proxy': {
'http': f'http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}',
'https': f'https://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}',
'no_proxy': 'localhost,127.0.0.1'
}
}
driver = webdriver.Chrome(seleniumwire_options=options)
driver.get("https://www.whatismyipaddress.com/")
time.sleep(5)
driver.close()
driver.quit()
Добавляйте задержки между кликами и прокрутками, имитируя поведение реального пользователя.
Предпочтительным способом реализации задержек является использование Expected Conditions. Скрипт автоматически продолжит работу, если обнаружит в DOM-дереве выбранный элемент.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://example.com")
# Ждём, пока кнопка станет кликабельной (до 10 секунд)
wait = WebDriverWait(driver, 10)
button = wait.until(EC.element_to_be_clickable((By.ID, "login-btn")))
button.click() # Только после появления элемента
Другой способ — использовать примитивные задержки через time.sleep()
. Но его проблема в том, что он не учитывает динамическое появление элементов.
Некоторые сайты определяют Selenium-ботов по стандартному User-Agent
, поэтому его стоит подменять. Это помогает замаскировать браузер под обычного пользователя. В Selenium можно задать кастомный User-Agent
через опции браузера:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
driver = webdriver.Chrome(options=chrome_options)
driver.get("https://www.whatismybrowser.com/")
Если знакомы с библиотекой fakeua
, которая каждый раз генерирует случайный User-Agent
, то можно применить её функционал в этом случае.
from fakeua import UserAgent
ua = UserAgent()
random_user_agent = ua.random()
print(f"Используем User-Agent: {random_user_agent}")
chrome_options.add_argument(f"user-agent={random_user_agent}")
Существуют сервисы, которые автоматически решают появляющуюся на странице капчу, будто это делает реальный человек. В том числе, можно автоматизировать решение капч с картинками, звуками, слайдерами. Алгоритм работы с ними всегда един:
Сделайте скриншот капчи. Или используйте метод, предлагаемый сервисом решения капчи.
Отправьте изображение сервису через API.
Получите ответ.
Выполните целевое действие — вставка ответа, выбор картинок, проведение по слайдеру.
Часто основной задачей парсинга бывает не просто извлечь информацию, но и надёжно её сохранить для последующей аналитики, отчётов или передачи другим сервисам. Рассмотрим наиболее удобные варианты.
Сперва установите библиотеку pandas
:
pip install pandas
Далее составьте алгоритм получения данных с исходного сайта-донора. Для выгрузки в формат табличного типа лучше всего использовать список словарей с одинаковыми ключами — они будут столбцами таблицы.
Рассмотрим пример сохранения в CSV:
import pandas as pd
# вместо data подставьте список с полученными данными
data = [
{"product_name": "Товар 1", "price": 100},
{"product_name": "Товар 2", "price": 200},
{"product_name": "Товар 3", "price": 300}
]
df = pd.DataFrame(data)
df.to_csv("results.csv", index=False, encoding="utf-8")
Для экспорта в Excel используйте функцию to_excel()
:
df.to_excel("results.xlsx", index=False)
Если нужно сохранять большие объёмы данных или использовать их для последующего анализа, удобнее сразу класть их в базу данных.
SQLite — локальная файловая база данных, не требует отдельного сервера. Подробно о ней мы говорили в другом руководстве.
Установка:
pip install sqlite3
В последних версиях Python модуль уже встроен.
Пример записи данных:
import sqlite3
conn = sqlite3.connect("results.db")
cursor = conn.cursor()
# пропишите структуру базы данных в SQL запросе
cursor.execute("CREATE TABLE IF NOT EXISTS products (name TEXT, price INTEGER)")
# вместо data подставьте список с полученными данными
data = [("Товар 1", 100), ("Товар 2", 200)]
# функция executemany() массово импортирует данные в БД
cursor.executemany("INSERT INTO products VALUES (?, ?)", data)
conn.commit()
conn.close()
В случае с MySQL ситуация похожа. Прежде установим библиотеку для работы с СУБД:
pip install mysql-connector-python
Процесс импорта данных совершенно идентичен, а подключение отличается:
import mysql.connector
conn = mysql.connector.connect(
host="localhost",
user="username",
password="password",
database="my_db"
)
cursor = conn.cursor()
Иногда результаты парсинга нужно получать быстро, в формате уведомлений. Например, если отслеживаете наличие билетов на мероприятие, когда организаторы выкладывают их небольшими партиями.
Парсер в этом случае удобно реализовать совместно с Telegram-ботом. Используем для этого фреймворк aiogram
. Сперва установим его:
pip install aiogram
Нужно зарегистрировать бота: мы уже говорили об этом в другой статье. Полученный токен нужно вставить как значение константы TOKEN
:
import asyncio
import logging
from aiogram import Bot, Dispatcher
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
TOKEN = "TELEGRAM_BOT_TOKEN"
CHAT_ID = "CHAT_ID"
URL = "https://tickets.example.com/event/1234"
CHECK_INTERVAL = 60 # Интервал проверки билетов (в секундах)
logging.basicConfig(level=logging.INFO)
bot = Bot(token=TOKEN)
dp = Dispatcher()
class TicketNotifier:
def __init__(self):
self.driver = self.init_selenium()
self.last_status = False # Запоминаем предыдущее состояние (чтобы не отправлять одно и то же)
def init_selenium(self):
""" Инициализация headless Selenium """
options = Options()
options.add_argument("--headless")
options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
return webdriver.Chrome(options=options)
def parse_site(self):
""" Парсинг сайта на наличие билетов """
try:
self.driver.get(URL)
wait = WebDriverWait(self.driver, 10)
element = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "ticket-available")))
return "Билеты в наличии" in element.text
except Exception as e:
logging.warning(f"Ошибка парсинга: {e}")
return False
async def check_tickets(self):
""" Фоновая задача для проверки билетов """
while True:
available = self.parse_site()
if available and not self.last_status: # Если билеты появились впервые
await self.notify()
self.last_status = True # Запоминаем, что уже отправляли сообщение
elif not available:
self.last_status = False # Если билетов нет, сбрасываем статус
await asyncio.sleep(CHECK_INTERVAL)
async def notify(self):
""" Отправка уведомления в Telegram-чат """
message = f"🎟 Билеты на мероприятие появились в продаже! 🔥\n[Купить билет]({URL})"
try:
await bot.send_message(CHAT_ID, message, parse_mode="Markdown")
logging.info(f"Уведомление отправлено в чат {CHAT_ID}")
except Exception as e:
logging.warning(f"Ошибка отправки сообщения: {e}")
async def run(self):
""" Запуск фоновой задачи и Telegram-бота """
asyncio.create_task(self.check_tickets()) # Фоновая задача для парсинга
await dp.start_polling(bot)
if __name__ == "__main__":
notifier = TicketNotifier()
asyncio.run(notifier.run())
Ошибка или симптом |
Причина |
Решение |
|
Элемент ещё не успел прогрузиться или находится в другом фрейме. |
используйте |
Версии Chromedriver и Chrome не совпадают: |
— |
Вручную скачайте подходящую версию с официального сайта или обновите браузер, чтобы он соответствовал драйверу. |
Пустые данные при попытке прочитать текст элементов. |
Код слишком быстро завершается (контент не успевает загрузиться). |
Добавляйте небольшие задержки или используйте «умные ожидания» ( |
«Permission denied» или «Sandbox» ошибки на серверах Linux |
Запущено в режиме контейнера или без соответствующих прав. |
Добавьте параметры |
На рынке существуют популярные альтернативы Selenium:
requests
+ BeautifulSoup
Актуально, если страница не генерируется динамически, а вся нужная информация доступна в исходном HTML.
Плюсы: выше скорость, меньше потребление ресурсов, удобная обработка HTML.
Минусы: не подходит для сложных сценариев (авторизация через JS, сложные клики).
Playwright
Более современный инструмент с поддержкой разных языков (включая Python). Может работать в headless-режиме, имеет встроенные методы для обхода блокировок.
Плюсы: быстрая многопоточность, удобные ожидания.
Минусы: относительно молодая библиотека, чуть меньше готовых примеров, чем у Selenium.
Puppeteer
Изначально разработан для Node.js (JavaScript), но существует Python-обёртка.
Плюсы: нативная интеграция с Chrome DevTools.
Минусы: Python-версия не такая стабильная, как оригинальная на JS.
Scrapy
Классический инструмент для больших парсинг-проектов, где важно удобное управление пауками (spiders
), логирование и масштабируемость.
Плюсы: легко управлять множеством URL, есть встроенные механизмы для работы с pipelin’ами данных.
Минусы: плохо подходит для сложных динамических сайтов (без надстроек).
Подготовили для вас выгодные тарифы на облачные серверы
При работе с динамическими сайтами Selenium оказывается незаменимым инструментом: он способен воспроизводить действия реального пользователя, так как он эмулирует полноценный браузер. Если позволяет структура сайта и данные доступны напрямую, иногда удобнее использовать менее ресурсозатратные инструменты, вроде requests
+ BeautifulSoup
. А в случаях, когда важна масштабируемость и удобное ведение проектов, на выручку приходят Scrapy
, Playwright
или Pyppeteer
.
Помните о легальности извлекаемых данных и проверять условия использования сайтов. Тщательно настраивайте окружение, маскируйте браузер, используйте прокси и сервисы антикапчи, чтобы снизить вероятность блокировок. При больших объёмах парсинга продумайте архитектуру сервера — дополнительные ядра и достаточное количество RAM повысят стабильность сборки данных.