Что такое Selenium - можно почитать здесь: https://habr.com/ru/post/152653/.
В двух словах - это комплекс решений, который позволяет программно управлять браузером в автоматическом режиме так, как-будто это делает живой человек.
Основным назначением продукта является организация автоматического тестирования, но можно придумать и что-то еще.
Selenium в настоящий момент поддерживает управление следующими браузерами:
Браузер | Операционная система | Разработчик |
---|---|---|
Chromium/Google Chrome | Windows/macOS/Linux | |
Firefox | Windows/macOS/Linux | Mozilla |
Edge | Windows 10 | Microsoft |
Internet Explorer | Windows | Selenium Project |
Safari | macOS El Capitan и более новые | Apple |
Opera | Windows/macOS/Linux | Opera |
В своих экспериментах я решил использовать связку Selenium + Chrome + Windows10.
При этом, основной проблемой являлось то, что с Selenium'ом работает только ограниченный круг third-part - библиотек (которые предоставляют сервис высокого уровня абстракции (читай - удобства)).
В частности, официально, поддерживаются такие языки:
- C#
- Ruby
- Java
- Pyton (конечно-же))
- JS
https://www.selenium.dev/downloads/
Гениальнейшая (без сарказма и иронии) платформа 1с, почему-то в перечень этих библиотек не входит(.
Покопавшись немного в документации, я нашел информацию, что на самом деле-то Selenium, при запуске, поднимает локальный web-server, который обладает развесистым низкоуровневым API. Сложность работы с ним состоит в том, что он именно "низкоуровневый", то-есть для получения результата требует много кода.
Вот его описание: https://www.w3.org/TR/webdriver/
Приготовившись читать много доки на не нашем языке, я приступил к делу.
1. Установка Selenium
Поскольку программный комплекс состоит как-бы из 3х звеньев (нативный web-driver + сервер Selenium + браузер), то имея уже установленный на компьютере Хром, мне осталось поставить Web-driver и сервер Selenium.
1.1. Установка Web-driver'а
Web-driver качается вот отсюда: https://chromedriver.chromium.org/
Однако, как оказалось, драйвер очень чувствителен к версии браузера, установленного на компьютере и чтобы подобрать и скачать нужную версию, требуется пройти квест. Почему они не сделали js-скрипт на странице, который сам определяет версию браузера и выдают нужный драйвер - я не понял. Короче вот: https://chromedriver.chromium.org/downloads/version-selection.
Если кратко, то чтобы скачанное запустилось, следует:
1. зайти в настройки браузера (3 точки - сверху, справа -- Настройки -- О браузере)
2. Из версии браузера убрать последнюю часть и получившееся добавить к строке адреса https://chromedriver.storage.googleapis.com/LATEST_RELEASE_.
Должно получиться так: https://chromedriver.storage.googleapis.com/LATEST_RELEASE_96.0.4664
3. Перейдя по получившемуся адресу, вы попадете на страницу с версией драйвера, которая подойдет под вашу версию браузера.
4. Решительно копируйте ее в буфер обмена и добавляйте к строке https://chromedriver.storage.googleapis.com/index.html?path=
Должно получиться так: https://chromedriver.storage.googleapis.com/index.html?path=96.0.4664.45/ (в конце еще следует добавить прямой слеш).
5. После перехода по полученному адресу, вы попадете на нужную страницу закачек, на которой следует выбрать драйвер под Вашу ОС.
Качаем и кладем в папочку (без кириллицы и без пробелов в пути к папочке - в общем все как обычно).
1.2. Установка сервера Selenium
Здесь все просто: https://www.selenium.dev/downloads/
Качаем отсюда и кидаем в ту-же папку, что и web-driver.
2. Запуск
В принципе, в простейшем случае (без организации грида (сетки из множества конечных узлов Selenium)) современная версия комплекса сама себя настраивает. Чтобы все заиграло одной машине, достаточно в каталоге со скачанными образами сервера и веб-драйвера создать bat-файл с таким вот содержимым:
//////////////////////
java -jar selenium-server-4.1.0.jar standalone
pause
//////////////////////
Произойдет запуск сервера в режиме одиночки.
Если все скачано и запущено прямо, то в запустившейся консоли не должно быть сообщений об ошибках).
По-умолчанию сервер стартует на порту 4444.
Теперь можно порулить.
3. Руление
3.1. Итак, сначала проверим: как он там.
Кидаем вот такой GET: http://localhost:4444/status (https://www.w3.org/TR/webdriver/#status) на localhost.
Если все ок, то должна быть возвращена структура, описывающая состояние сервера:
{
"value": {
"ready": true,
"message": "Selenium Grid ready.",
"nodes": [
{
"id": "10811e6b-17dc-4163-bead-a38221dbc603",
"uri": "http://ххх.ххх.ххх.ххх:4444",
"maxSessions": 6,
"osInfo": {
"arch": "amd64",
"name": "Windows 10",
"version": "10.0"
},
"heartbeatPeriod": 60000,
"availability": "UP",
"version": "4.1.0 (revision 87802e897b)",
"slots": [
{
"lastStarted": "1970-01-01T00:00:00Z",
"session": null,
"id": {
"hostId": "10811e6b-17dc-4163-bead-a38221dbc603",
"id": "b088a136-98d4-4b06-91c4-6b5a44c71a1d"
},
"stereotype": {
"browserName": "chrome",
"platformName": "WIN10"
}
},
{
"lastStarted": "1970-01-01T00:00:00Z",
"session": null,
"id": {
"hostId": "10811e6b-17dc-4163-bead-a38221dbc603",
"id": "f7f30980-09c3-481d-a202-9d8ae6600aa4"
},
"stereotype": {
"browserName": "chrome",
"platformName": "WIN10"
}
},
{
"lastStarted": "1970-01-01T00:00:00Z",
"session": null,
"id": {
"hostId": "10811e6b-17dc-4163-bead-a38221dbc603",
"id": "a820aa7d-5e18-4162-8c93-990c362e2d18"
},
"stereotype": {
"browserName": "chrome",
"platformName": "WIN10"
}
},
{
"lastStarted": "1970-01-01T00:00:00Z",
"session": null,
"id": {
"hostId": "10811e6b-17dc-4163-bead-a38221dbc603",
"id": "28bf1c2b-f351-44a2-9bdb-ea2e029e3fb1"
},
"stereotype": {
"browserName": "chrome",
"platformName": "WIN10"
}
},
{
"lastStarted": "1970-01-01T00:00:00Z",
"session": null,
"id": {
"hostId": "10811e6b-17dc-4163-bead-a38221dbc603",
"id": "a50d9495-831a-4148-89b1-8723608a3053"
},
"stereotype": {
"browserName": "chrome",
"platformName": "WIN10"
}
},
{
"lastStarted": "1970-01-01T00:00:00Z",
"session": null,
"id": {
"hostId": "10811e6b-17dc-4163-bead-a38221dbc603",
"id": "659d6bc3-7463-4c16-88f7-27395179fe62"
},
"stereotype": {
"browserName": "chrome",
"platformName": "WIN10"
}
}
]
}
]
}
}
Ключевой момент здесь: "ready": true.
3.2. Теперь запускаем экземпляр сеанса (по-факту открывается Хром)
Для этого кидаем такой POST: http://localhost:4444/session (https://www.w3.org/TR/webdriver/#new-session)
с таким телом запроса:
{
"capabilities": {
"firstMatch": [
{
"browserName": "chrome",
"pageLoadStrategy": "eager"
}
],
"timeouts": {
"Script Timeout": "1800,000",
"Page Load Timeout": "1800,000",
"Implicit Wait Timeout": "1800,000"
}
}
}
"pageLoadStrategy = игорь"
- означает, что при переходе по адресу, сервер не вернет ответ, пока не закончится загрузка страницы и не отработают на ней все скрипты. Это очень важный момент для реализации парсинга страницы.
После этого волшебным образом должно открыться окно браузера, а ответ должен быть такой:
{
"value": {
"sessionId": "bdfafa0bcbac62421cc184328f3b44bb",
"capabilities": {
"acceptInsecureCerts": false,
"browserName": "chrome",
"browserVersion": "96.0.4664.110",
"chrome": {
"chromedriverVersion": "96.0.4664.45 (76e4c1bb2ab4671b8beba3444e61c0f17584b2fc-refs/branch-heads/4664@{#947})",
"userDataDir": "C:\\Users\\ХХХХХХХ\\AppData\\Local\\Temp\\scoped_dir48368_636555220"
},
"goog:chromeOptions": {
"debuggerAddress": "localhost:65126"
},
"networkConnectionEnabled": false,
"pageLoadStrategy": "eager",
"platformName": "windows",
"proxy": {
},
"se:cdp": "ws://ххх.ххх.ххх.ххх:4444/session/187533817ed102e7eb1b766d3a467194/se/cdp",
"se:cdpVersion": "96.0.4664.110",
"setWindowRect": true,
"strictFileInteractability": false,
"timeouts": {
"implicit": 0,
"pageLoad": 300000,
"script": 30000
},
"unhandledPromptBehavior": "dismiss and notify",
"webauthn:extension:credBlob": true,
"webauthn:extension:largeBlob": true,
"webauthn:virtualAuthenticators": true
}
}
}
Больше всего из этого нам важно поле "sessionId". На основе sessionId строятся все последующие запросы управления.
3.3. Заставим Хром открыть страницу Яндекса
POST: http://localhost:4444/session/bdfafa0bcbac62421cc184328f3b44bb/url (https://www.w3.org/TR/webdriver/#navigate-to)
Тело:
{"url": "https://yandex.ru/"}
Браузер должен открыть страницу ИТ-гиганта, а запрос вернуть такой JSON:
{
"value": null
}
3.4. Найдем на странице заголовки новостей
Согласно документации, существует множество разнообразных стратегий поиска элементов на странице.
В самом простом случае можно искать по ID, по Классу или все вместе - используя выражения xPath.
https://www.w3.org/TR/webdriver/#find-elements
Кидаем POST: http://localhost:4444/session/bdfafa0bcbac62421cc184328f3b44bb/elements
Тело:
{
"using": "xpath",
"value": "//span[contains(@class,'news__item-content')]"
}
Разбор синтаксиса xpath выходит за рамки этого исследования, так-что просто напишу, что данное выражение ищет по всему дереву элементов html-страницы элементы "span" с классом 'news__item-content' (это заголовки новостей).
Запрос возвращает такой ответ:
{
"value": [
{
"element-6066-11e4-a52e-4f735466cecf": "714dd669-9d30-41ba-a02d-3c0d4fde3ccf"
},
{
"element-6066-11e4-a52e-4f735466cecf": "cffcd89f-7403-4368-a4fc-8da4374002a8"
},
{
"element-6066-11e4-a52e-4f735466cecf": "d150c945-cd8f-4342-a605-1e79cdfe8f1e"
},
{
"element-6066-11e4-a52e-4f735466cecf": "c452dcf6-500b-4b9c-8aee-c6034e636ca5"
},
{
"element-6066-11e4-a52e-4f735466cecf": "27f63f2c-b393-4646-804d-a47a38d0f320"
},
{
"element-6066-11e4-a52e-4f735466cecf": "1383eb44-9398-476b-a2c4-88621e8584e3"
},
{
"element-6066-11e4-a52e-4f735466cecf": "7729a7e1-27ae-43fb-9ff9-1b4dae4cbe4d"
},
{
"element-6066-11e4-a52e-4f735466cecf": "6c5cea92-4b7f-4697-a71a-4733938f030b"
},
{
"element-6066-11e4-a52e-4f735466cecf": "da63d035-d2f4-43f3-80c6-d14c1cdb7781"
},
{
"element-6066-11e4-a52e-4f735466cecf": "e31e1bd3-50cf-4cee-b415-6b8a782c0e91"
}
]
}
Это перечень идентификаторов найденных элементов.
3.5. Теперь надо-бы получить текст из этих элементов
Для этой цели у Selenium'а запасен запрос https://www.w3.org/TR/webdriver/#get-element-text
Он тупо выдергивает подчиненный текст элемента (если он есть).
Get: http://localhost:4444/session/bdfafa0bcbac62421cc184328f3b44bb/element/714dd669-9d30-41ba-a02d-3c0d4fde3ccf/text
Ответ:
{
"value": "Генеральная прокуратура обвинила «Мемориал» в реабилитации нацистских преступников"
}
Элементы можно обойти в цикле и сложить в массив все строки, что попались - с проверкой на пустые строки.
Вообще, при парсинге бывает много дивных неожиданностей, которые обусловлены сложностью современных страниц и ошибками разрабов.
3.6. В завершении, по-хорошему следует грохнуть сессию
https://www.w3.org/TR/webdriver/#delete-session
Если внимательно почитать документацию, то там есть еще много замечательных возможностей: https://www.w3.org/TR/webdriver/#state, https://www.w3.org/TR/webdriver/#element-interaction (взаимодействие с элементами) и т.д.
В заключении приложу к статье базу данных с реализацией всего вышеописанного в коде 1с.
Тестировалось все на платформе 1с версии 8.3.18.1334 x64.