CoderCastrov logo
CoderCastrov
Selenium

Парсинг динамического HTML в Python с использованием Selenium

Парсинг динамического HTML в Python с использованием Selenium
просмотров
4 мин чтение
#Selenium

Когда веб-страница открывается в браузере, браузер автоматически выполняет JavaScript и генерирует динамический HTML-контент. Обычно для получения веб-страницы делается HTTP-запрос. Однако, если веб-страница генерируется динамически с помощью JavaScript, HTTP-запрос получит только исходный код страницы. Многие веб-сайты используют технологию Ajax для отправки и получения данных с сервера без перезагрузки страницы. Чтобы спарсить веб-страницы, использующие Ajax, не потеряв при этом никаких данных, одно из решений - выполнить JavaScript с помощью пакетов Python и спарсить полностью загруженную веб-страницу. Selenium - мощный инструмент для автоматизации браузеров и загрузки веб-страниц с возможностью выполнения JavaScript.

1. Запуск Selenium с WebDriver

Selenium не содержит собственного веб-браузера. Он вызывает API на WebDriver, который открывает браузер. У Firefox и Chrome есть свои собственные WebDriver, которые взаимодействуют с Selenium. Если вам не нужен пользовательский интерфейс браузера, хорошим вариантом является PhantomJS, который загружает веб-страницы и выполняет JavaScript в фоновом режиме. В следующем примере я буду использовать Chrome WebDriver.

Перед запуском Selenium с WebDriver установите Selenium pip install Selenium и загрузите Chrome WebDriver

Запустите Selenium с WebDriver. Запустив следующий код, откроется браузер Chrome.

from selenium import WebDriver
driver = WebDriver.Chrome('./chromedriver') #указываем путь к WebDriver

2. Динамический HTML

Давайте возьмем в качестве примера эту веб-страницу: https://www.u-optic.com/plano-convex-spherical-lens/en.html. На этой странице выполняются запросы Ajax для получения данных и динамической генерации содержимого страницы. Предположим, нас интересуют данные, перечисленные в HTML-таблице. Они отсутствуют в исходном коде HTML. Простой HTTP-запрос вернет только исходный код страницы без данных.

Более подробно рассмотрим таблицу, созданную JavaScript в браузере:

3. Начать парсинг

Есть два способа парсить динамический HTML. Более очевидный способ - загрузить страницу в Selenium WebDriver. WebDriver автоматически выполняет запросы Ajax и затем генерирует полную веб-страницу. После полной загрузки веб-страницы используйте Selenium для получения исходного кода страницы, в котором содержатся данные.

Однако на примере веб-страницы из-за пагинации таблица показывает только 10 записей. Для получения всех записей необходимо выполнить несколько запросов Ajax.

Изучите веб-страницу, во вкладке "Network" найдите 2 запроса Ajax, с помощью которых веб-страница загружает данные для построения таблиц.

Скопируйте и вставьте URL-адреса в браузер или выполните HTTP-запросы с использованием библиотеки Python Requests, чтобы получить 10 записей в формате JSON.

{"draw":1,"recordsTotal":1564,"recordsFiltered":1564,"data":[{"id":66,"material_code":"4001010101","model":..."}]}

Возвращенные данные в формате JSON указывают, что всего есть 1564 записи. При более детальном рассмотрении URL-адреса Ajax можно увидеть, что количество записей, которые нужно получить, указывается в параметре "length" в URL-адресе.

В первой таблице 62 элемента, а во второй таблице 1564 элемента. Таким образом, измените значение параметра "length" в URL-адресе соответствующим образом.

Запрос данных напрямую гораздо удобнее, чем парсить данные со веб-страниц с использованием Xpath или CSS-селекторов.

4. Поиск URL-адресов запросов Ajax в журналах WebDriver

URL-адреса запросов Ajax скрыты внутри кода JavaScript. Мы можем искать их в журналах производительности WebDriver, которые записывают события для запросов Ajax. Чтобы получить журналы производительности из WebDriver, мы должны указать аргумент при создании объекта WebDriver:

from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
caps = DesiredCapabilities.CHROME
caps['goog:loggingPrefs'] = {'performance': 'ALL'}
driver = webdriver.Chrome('./chromedriver', desired_capabilities=caps)
driver.get('https://www.u-optic.com/plano-convex-spherical-lens/en.html')
log = driver.get_log('performance')

Журнал производительности записывает сетевую активность, которую выполнил WebDriver при загрузке веб-страницы.

[{'level': 'INFO', 'message': '{"message":{"method":"Network.responseReceivedExtraInfo","params":{"..."}', 'timestamp': 1596881833630}, {'level': 'INFO', 'message': '{"message":{"method":"Network.responseReceived","params":{"..."}'' ]

Значение ключа "message" является строкой в формате JSON. Разберем эту строку с помощью модуля json в Python, и мы найдем запросы Ajax для получения данных, которые выполняются с помощью метода "Network.requestWillBeSent". URL-адрес имеет путь "/api/diy/get_product_by_type".

{ 'method': 'Network.requestWillBeSent', 'params': { .... 'request': { ... 'url': 'https://www.u-optic.com/api/diy/get_product_by_type?...start=0&length=10...' }, ... } }

Мы используем регулярное выражение для поиска этих URL-адресов.

import json
import re
pattern = r'https\:\/\/www\.u\-optic\.com\/api\/diy\/get\_product\_by\_type.+'
urls = list() # список для хранения URL-адресов Ajax
for entry in log:
    message = json.loads(entry['message'])
    if message['message']['method'] == 'Network.requestWillBeSent':
        if re.search(pattern, message['message']['params']['request']['url']):
            urls.append(message['message']['params']['request']['url'])

Дополнительные заметки:

Когда WebDriver загружает веб-страницу, может потребоваться несколько секунд, чтобы WebDriver выполнить запросы Ajax и сгенерировать содержимое страницы. Поэтому рекомендуется настроить WebDriver на ожидание некоторое время, пока раздел, который мы собираемся парсить, полностью загрузится. В этом примере мы хотим спарсить данные в таблице. Данные находятся в классе "text-bold". Поэтому мы устанавливаем WebDriver на ожидание 5 секунд, пока класс 'text-bold' не загрузится. Если раздел не загружается в течение 5 секунд, будет сгенерировано исключение TimeoutException.

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

wait_elementid = "//a[@class='text-bold']"
wait_time = 5
WebDriverWait(self.driver, wait_time).until(EC.visibility_of_element_located((By.XPATH, wait_elementid)))

5. Заключение

Динамически генерируемые веб-страницы отличаются от своего исходного кода, поэтому мы не можем спарсить их с помощью HTTP-запросов. Выполнение JavaScript с помощью Selenium - это решение для парсинга веб-страниц без потери данных. Кроме того, если данные, которые нужно спарсить, получаются через Ajax-запросы, мы можем искать URL-адреса запросов в журналах производительности WebDriver и получать данные напрямую, выполняя HTTP-запросы.