- Home »

Учебник по Selenium WebDriver: начало работы
Итак, ты наверняка слышал о Selenium WebDriver — это мощный инструмент для автоматизации веб-браузеров, который стал стандартом де-факто для тестирования веб-приложений. Но если ты администратор серверов или DevOps-инженер, то знаешь, что возможности Selenium простираются далеко за пределы простого тестирования. Мониторинг веб-сервисов, автоматизация рутинных задач в браузере, сбор данных — всё это можно делать с помощью WebDriver. В этой статье я покажу, как быстро развернуть Selenium на твоём сервере и начать использовать его для автоматизации. Мы пройдём от установки до реальных примеров использования, разберём подводные камни и рассмотрим альтернативы.
Как работает Selenium WebDriver
WebDriver — это API для управления браузерами программно. В отличие от старого Selenium RC, который работал через JavaScript инъекции, WebDriver напрямую взаимодействует с браузером через его нативные методы. Это означает более стабильную работу и лучшую производительность.
Архитектура выглядит следующим образом:
- Selenium Client — твой Python/Java/C# код
- WebDriver Protocol — HTTP REST API для связи
- Driver — ChromeDriver, GeckoDriver, EdgeDriver
- Browser — Chrome, Firefox, Edge и т.д.
Когда ты пишешь driver.get("https://example.com")
, происходит следующее:
- Selenium клиент отправляет HTTP POST запрос к WebDriver
- WebDriver переводит команду в вызовы браузерного API
- Браузер выполняет действие и возвращает результат
- WebDriver возвращает ответ обратно в твой код
Установка и настройка на сервере
Для серверного использования понадобится headless-режим (без графического интерфейса). Если у тебя ещё нет подходящего сервера, можешь взять VPS или выделенный сервер.
Начнём с установки на Ubuntu/Debian:
# Обновляем систему
sudo apt update && sudo apt upgrade -y
# Устанавливаем Python и pip
sudo apt install python3 python3-pip -y
# Устанавливаем Google Chrome (headless)
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
sudo apt update
sudo apt install google-chrome-stable -y
# Устанавливаем Selenium
pip3 install selenium webdriver-manager
# Проверяем установку Chrome
google-chrome --version
Для CentOS/RHEL процесс немного отличается:
# Добавляем репозиторий Chrome
sudo tee /etc/yum.repos.d/google-chrome.repo << EOF
[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/x86_64
enabled=1
gpgcheck=1
gpgkey=https://dl.google.com/linux/linux_signing_key.pub
EOF
# Устанавливаем Chrome
sudo yum install google-chrome-stable -y
# Устанавливаем Python и зависимости
sudo yum install python3 python3-pip -y
pip3 install selenium webdriver-manager
Первый скрипт и базовые операции
Создаём тестовый скрипт для проверки работоспособности:
#!/usr/bin/env python3
# test_selenium.py
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
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
import time
def create_driver():
"""Создаём WebDriver с оптимальными настройками для сервера"""
options = Options()
# Headless режим (без GUI)
options.add_argument('--headless')
# Отключаем GPU (для серверов без видеокарты)
options.add_argument('--no-gpu')
# Отключаем песочницу (нужно для запуска от root)
options.add_argument('--no-sandbox')
# Отключаем /dev/shm (решает проблемы с памятью)
options.add_argument('--disable-dev-shm-usage')
# Устанавливаем размер окна
options.add_argument('--window-size=1920,1080')
# Отключаем уведомления
options.add_argument('--disable-notifications')
# Автоматически загружаем нужную версию ChromeDriver
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=options)
driver.set_page_load_timeout(30)
return driver
def test_basic_functionality():
"""Тестируем базовый функционал"""
driver = create_driver()
try:
# Переходим на страницу
print("Загружаем https://httpbin.org/html")
driver.get("https://httpbin.org/html")
# Ждём загрузки элемента
wait = WebDriverWait(driver, 10)
h1_element = wait.until(EC.presence_of_element_located((By.TAG_NAME, "h1")))
# Получаем заголовок страницы
print(f"Заголовок страницы: {driver.title}")
print(f"Текст H1: {h1_element.text}")
# Делаем скриншот
driver.save_screenshot('/tmp/test_screenshot.png')
print("Скриншот сохранён в /tmp/test_screenshot.png")
# Получаем HTML
html = driver.page_source
print(f"Размер HTML: {len(html)} символов")
return True
except Exception as e:
print(f"Ошибка: {e}")
return False
finally:
driver.quit()
if __name__ == "__main__":
success = test_basic_functionality()
print(f"Тест {'прошёл' if success else 'провалился'}")
Запускаем тест:
python3 test_selenium.py
Реальные кейсы использования
Теперь рассмотрим практические примеры, которые пригодятся в повседневной работе сисадмина:
Мониторинг веб-сервисов
#!/usr/bin/env python3
# monitor_service.py
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
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
import time
import smtplib
from email.mime.text import MIMEText
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class WebServiceMonitor:
def __init__(self):
self.driver = None
self.setup_driver()
def setup_driver(self):
options = Options()
options.add_argument('--headless')
options.add_argument('--no-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--window-size=1920,1080')
service = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=service, options=options)
self.driver.set_page_load_timeout(30)
def check_login_form(self, url, username_field, password_field):
"""Проверяем доступность формы логина"""
try:
self.driver.get(url)
wait = WebDriverWait(self.driver, 10)
# Проверяем наличие полей
username_input = wait.until(EC.presence_of_element_located((By.NAME, username_field)))
password_input = wait.until(EC.presence_of_element_located((By.NAME, password_field)))
logger.info(f"✓ Форма логина на {url} доступна")
return True
except Exception as e:
logger.error(f"✗ Ошибка при проверке {url}: {e}")
return False
def check_element_text(self, url, selector, expected_text):
"""Проверяем содержимое элемента"""
try:
self.driver.get(url)
wait = WebDriverWait(self.driver, 10)
element = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
actual_text = element.text
if expected_text in actual_text:
logger.info(f"✓ Текст '{expected_text}' найден на {url}")
return True
else:
logger.error(f"✗ Ожидался '{expected_text}', получен '{actual_text}'")
return False
except Exception as e:
logger.error(f"✗ Ошибка при проверке {url}: {e}")
return False
def performance_check(self, url):
"""Проверяем производительность загрузки"""
try:
start_time = time.time()
self.driver.get(url)
# Ждём полной загрузки
WebDriverWait(self.driver, 30).until(
lambda driver: driver.execute_script("return document.readyState") == "complete"
)
load_time = time.time() - start_time
logger.info(f"Время загрузки {url}: {load_time:.2f} секунд")
return load_time
except Exception as e:
logger.error(f"Ошибка при проверке производительности {url}: {e}")
return None
def cleanup(self):
if self.driver:
self.driver.quit()
# Пример использования
if __name__ == "__main__":
monitor = WebServiceMonitor()
try:
# Проверяем админку
admin_ok = monitor.check_login_form(
"https://your-admin-panel.com/login",
"username",
"password"
)
# Проверяем главную страницу
main_ok = monitor.check_element_text(
"https://your-site.com",
"h1",
"Welcome"
)
# Проверяем производительность
load_time = monitor.performance_check("https://your-site.com")
if not admin_ok or not main_ok or (load_time and load_time > 5):
logger.error("Обнаружены проблемы с сервисом!")
# Здесь можно добавить отправку уведомлений
finally:
monitor.cleanup()
Автоматизация рутинных задач
#!/usr/bin/env python3
# auto_tasks.py
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, Select
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
import time
import json
import os
class ServerAutoTasks:
def __init__(self):
self.driver = None
self.setup_driver()
def setup_driver(self):
options = Options()
options.add_argument('--headless')
options.add_argument('--no-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
# Настройки для загрузки файлов
prefs = {
"download.default_directory": "/tmp/downloads",
"download.prompt_for_download": False,
"download.directory_upgrade": True,
"safebrowsing.enabled": True
}
options.add_experimental_option("prefs", prefs)
service = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=service, options=options)
self.driver.set_page_load_timeout(30)
def download_ssl_certificate(self, domain):
"""Скачиваем SSL сертификат для домена"""
try:
# Используем онлайн-сервис для получения сертификата
self.driver.get("https://www.ssllabs.com/ssltest/")
# Вводим домен
hostname_input = self.driver.find_element(By.NAME, "d")
hostname_input.clear()
hostname_input.send_keys(domain)
# Запускаем тест
submit_button = self.driver.find_element(By.CSS_SELECTOR, "input[type='submit']")
submit_button.click()
# Ждём результатов (это может занять время)
wait = WebDriverWait(self.driver, 300) # 5 минут
# Проверяем статус
try:
grade_element = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".grade"))
)
grade = grade_element.text
print(f"SSL рейтинг для {domain}: {grade}")
# Сохраняем скриншот результата
self.driver.save_screenshot(f"/tmp/ssl_test_{domain}.png")
return grade
except:
print(f"Не удалось получить рейтинг SSL для {domain}")
return None
except Exception as e:
print(f"Ошибка при проверке SSL для {domain}: {e}")
return None
def backup_website_content(self, url, output_dir):
"""Создаём резервную копию контента сайта"""
try:
self.driver.get(url)
# Получаем все ссылки
links = self.driver.find_elements(By.TAG_NAME, "a")
hrefs = [link.get_attribute("href") for link in links if link.get_attribute("href")]
# Получаем все изображения
images = self.driver.find_elements(By.TAG_NAME, "img")
img_srcs = [img.get_attribute("src") for img in images if img.get_attribute("src")]
# Сохраняем HTML
html_content = self.driver.page_source
# Создаём директорию для бэкапа
os.makedirs(output_dir, exist_ok=True)
# Сохраняем основной HTML
with open(f"{output_dir}/index.html", "w", encoding="utf-8") as f:
f.write(html_content)
# Сохраняем метаданные
metadata = {
"url": url,
"title": self.driver.title,
"timestamp": time.time(),
"links": hrefs,
"images": img_srcs
}
with open(f"{output_dir}/metadata.json", "w", encoding="utf-8") as f:
json.dump(metadata, f, indent=2, ensure_ascii=False)
print(f"Бэкап {url} сохранён в {output_dir}")
return True
except Exception as e:
print(f"Ошибка при создании бэкапа {url}: {e}")
return False
def cleanup(self):
if self.driver:
self.driver.quit()
# Пример использования
if __name__ == "__main__":
tasks = ServerAutoTasks()
try:
# Проверяем SSL сертификат
ssl_grade = tasks.download_ssl_certificate("google.com")
# Создаём бэкап сайта
tasks.backup_website_content("https://httpbin.org/html", "/tmp/backup")
finally:
tasks.cleanup()
Сравнение с альтернативными решениями
Selenium не единственный инструмент для автоматизации браузеров. Рассмотрим основные альтернативы:
Инструмент | Плюсы | Минусы | Лучший случай использования |
---|---|---|---|
Selenium WebDriver |
• Поддержка всех браузеров • Огромное сообщество • Множество языков • Стабильность |
• Медленная работа • Большое потребление ресурсов • Сложная настройка headless |
Комплексное тестирование, долгосрочные проекты |
Playwright |
• Быстрая работа • Современный API • Встроенная поддержка headless • Автоматическое ожидание |
• Относительно новый • Меньше плагинов • Ограниченная поддержка старых браузеров |
Современные веб-приложения, быстрые тесты |
Puppeteer |
• Очень быстрый • Отличная интеграция с Chrome • Богатый API • Хорошая документация |
• Только Chrome/Chromium • Только JavaScript • Нет поддержки Firefox/Safari |
Chrome-специфичные задачи, скрапинг |
Requests + BeautifulSoup |
• Очень быстро • Минимальное потребление ресурсов • Простота использования |
• Нет поддержки JavaScript • Нет взаимодействия с UI • Проблемы с SPA |
Простой скрапинг статических сайтов |
Оптимизация для серверного использования
При использовании Selenium на продакшн-серверах важно учесть несколько моментов:
Настройка ресурсов
# Скрипт для мониторинга ресурсов
#!/bin/bash
# monitor_selenium.sh
echo "=== Мониторинг Selenium процессов ==="
echo "Chrome процессы:"
ps aux | grep chrome | grep -v grep
echo -e "\nИспользование памяти:"
free -h
echo -e "\nИспользование CPU:"
top -bn1 | grep "Cpu(s)"
echo -e "\nДисковое пространство:"
df -h | grep -E "(Filesystem|/dev/)"
echo -e "\nСетевые соединения:"
netstat -tulpn | grep -E "(chrome|selenium)" | wc -l
Настройка лимитов
# /etc/security/limits.conf
selenium soft nofile 65536
selenium hard nofile 65536
selenium soft nproc 32768
selenium hard nproc 32768
# systemd service пример
# /etc/systemd/system/selenium-monitor.service
[Unit]
Description=Selenium Monitoring Service
After=network.target
[Service]
Type=simple
User=selenium
Group=selenium
WorkingDirectory=/opt/selenium
ExecStart=/usr/bin/python3 /opt/selenium/monitor.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# Ограничения ресурсов
MemoryLimit=1G
CPUQuota=50%
[Install]
WantedBy=multi-user.target
Docker-контейнер для изоляции
# Dockerfile
FROM ubuntu:20.04
# Устанавливаем зависимости
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
wget \
curl \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Устанавливаем Chrome
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update \
&& apt-get install -y google-chrome-stable \
&& rm -rf /var/lib/apt/lists/*
# Устанавливаем Python зависимости
RUN pip3 install selenium webdriver-manager
# Создаём пользователя
RUN useradd -m -s /bin/bash selenium
# Копируем скрипты
COPY scripts/ /home/selenium/
RUN chown -R selenium:selenium /home/selenium/
USER selenium
WORKDIR /home/selenium
CMD ["python3", "monitor.py"]
Запуск контейнера:
# Сборка
docker build -t selenium-monitor .
# Запуск
docker run -d \
--name selenium-monitor \
--memory=1g \
--cpus=0.5 \
--restart=unless-stopped \
-v /tmp/selenium-logs:/home/selenium/logs \
selenium-monitor
Полезные трюки и хаки
Обход anti-bot защиты
def create_stealth_driver():
"""Создаём WebDriver с настройками для обхода базовой защиты"""
options = Options()
# Базовые настройки
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
# Антидетект настройки
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument('--disable-extensions')
# Имитируем реального пользователя
options.add_argument('--user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36')
driver = webdriver.Chrome(options=options)
# Удаляем webdriver property
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
return driver
Параллельное выполнение задач
import concurrent.futures
import threading
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
class SeleniumPool:
def __init__(self, max_workers=4):
self.max_workers = max_workers
self.local_driver = threading.local()
def get_driver(self):
if not hasattr(self.local_driver, 'driver'):
options = Options()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
self.local_driver.driver = webdriver.Chrome(options=options)
return self.local_driver.driver
def check_url(self, url):
"""Проверяем одну URL"""
driver = self.get_driver()
try:
driver.get(url)
return {
'url': url,
'title': driver.title,
'status': 'ok',
'load_time': driver.execute_script("return performance.timing.loadEventEnd - performance.timing.navigationStart")
}
except Exception as e:
return {
'url': url,
'status': 'error',
'error': str(e)
}
def check_multiple_urls(self, urls):
"""Проверяем множество URLs параллельно"""
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
results = list(executor.map(self.check_url, urls))
return results
def cleanup(self):
if hasattr(self.local_driver, 'driver'):
self.local_driver.driver.quit()
# Использование
pool = SeleniumPool(max_workers=4)
urls = ['https://google.com', 'https://github.com', 'https://stackoverflow.com']
results = pool.check_multiple_urls(urls)
for result in results:
print(f"{result['url']}: {result.get('title', result.get('error'))}")
pool.cleanup()
Интеграция с системами мониторинга
Для продакшн-использования важно интегрировать Selenium с системами мониторинга:
Prometheus метрики
from prometheus_client import Counter, Histogram, start_http_server
import time
# Метрики
selenium_requests_total = Counter('selenium_requests_total', 'Total selenium requests', ['status'])
selenium_request_duration = Histogram('selenium_request_duration_seconds', 'Request duration')
class MonitoredSelenium:
def __init__(self):
self.driver = self.create_driver()
# Запускаем HTTP сервер для метрик
start_http_server(8000)
@selenium_request_duration.time()
def monitored_get(self, url):
try:
self.driver.get(url)
selenium_requests_total.labels(status='success').inc()
return True
except Exception as e:
selenium_requests_total.labels(status='error').inc()
raise e
Логирование в ELK Stack
import logging
import json
from datetime import datetime
# Настройка логирования в JSON формате для Logstash
class JSONFormatter(logging.Formatter):
def format(self, record):
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'level': record.levelname,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
'line': record.lineno
}
if hasattr(record, 'url'):
log_entry['url'] = record.url
if hasattr(record, 'duration'):
log_entry['duration'] = record.duration
if hasattr(record, 'error'):
log_entry['error'] = record.error
return json.dumps(log_entry)
# Настройка логгера
logger = logging.getLogger('selenium_monitor')
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# Использование
def monitored_action(url):
start_time = time.time()
try:
# Selenium действия
result = perform_selenium_action(url)
duration = time.time() - start_time
logger.info("Selenium action completed",
extra={'url': url, 'duration': duration})
return result
except Exception as e:
duration = time.time() - start_time
logger.error("Selenium action failed",
extra={'url': url, 'duration': duration, 'error': str(e)})
raise
Статистика и производительность
По данным различных бенчмарков:
- Selenium WebDriver: ~2-5 секунд на страницу, 50-200 МБ RAM на браузер
- Playwright: ~1-3 секунды на страницу, 30-150 МБ RAM на браузер
- Puppeteer: ~1-2 секунды на страницу, 40-120 МБ RAM на браузер
- Requests: ~0.1-0.5 секунд на страницу, 10-50 МБ RAM
Для серверного использования рекомендую следующую конфигурацию:
- CPU: минимум 2 ядра, рекомендуется 4+ для параллельного выполнения
- RAM: минимум 2 ГБ, рекомендуется 4+ ГБ для стабильной работы
- Дисковое пространство: минимум 10 ГБ для Chrome и логов
- Сеть: стабильное соединение 10+ Мбит/с
Новые возможности и интересные факты
Selenium 4 принёс много интересных возможностей:
- Относительные локаторы — можно искать элементы относительно других:
driver.find_element(with_tag_name("input").above(password_field))
- Новый API для скриншотов — можно делать скриншоты отдельных элементов
- Улучшенная поддержка мобильных устройств — эмуляция touch событий
- Интеграция с Docker — официальные образы для headless режима
Нестандартные способы использования:
- Генерация PDF из веб-страниц — используй
driver.execute_cdp_cmd('Page.printToPDF', {})
- Перехват сетевых запросов — с помощью Chrome DevTools Protocol
- Автоматизация SSH-туннелей — для доступа к внутренним админкам
- Создание динамических дашбордов — скриншоты графиков по расписанию
Интересные факты:
- Selenium первоначально создавался для тестирования внутренних приложений ThoughtWorks в 2004 году
- Название происходит от элемента селена, который используется для тестирования отравления ртутью (намёк на Mercury QTP)
- Google Chrome команда активно участвует в развитии WebDriver стандарта
- Selenium Grid может управлять тысячами браузеров одновременно
Заключение и рекомендации
Selenium WebDriver остаётся одним из самых мощных инструментов для автоматизации браузеров, особенно в серверной среде. Несмотря на появление более быстрых альтернатив, его универсальность и зрелость экосистемы делают его отличным выбором для большинства задач.
Рекомендую использовать Selenium когда:
- Нужна поддержка разных браузеров
- Требуется стабильное решение для долгосрочного проекта
- Команда уже знакома с инструментом
- Нужна интеграция с существующими тестовыми фреймворками
Стоит рассмотреть альтернативы если:
- Критична производительность (выбирай Playwright или Puppeteer)
- Работаешь только с простыми статическими сайтами (хватит Requests)
- Нужны продвинутые возможности современного веба (Playwright)
Для продакшн-использования обязательно:
- Используй Docker для изоляции
- Настрой мониторинг ресурсов
- Реализуй ротацию логов
- Добавь алерты на критические ошибки
- Регулярно обновляй драйверы браузеров
Selenium WebDriver — это не просто инструмент для тестирования, это целая экосистема для автоматизации веб-задач. Правильно настроенный и оптимизированный, он станет незаменимым помощником в твоей серверной инфраструктуре.
Полезные ссылки:
- Официальная документация Selenium Python
- Selenium на GitHub
- Selenium Project
- ChromeDriver
- GeckoDriver для Firefox
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.