Статичный WEB постепенно уходит в прошлое. Вслед за простым HTML плавно покидает нас и привычный многим «монолит» — это когда результирующий HTML для динамических сайтов генерировался силами сервера и передавался клиентам и их браузерам в готовом виде. Многие крупные сайты и веб-сервисы теперь полностью написаны на JavaScript. Если вы попытаетесь загрузить их HTML-код, то сильно удивитесь: кроме тега <HEAD> и <BODY> там ничего нет. Их содержимое — это ссылки на большое количество скриптов. Они загружаются непосредственно в браузере и выполняются в нём же, формируя по мере необходимости нужный HTML-код.
Всё это существенно усложняет процесс парсинга. Теперь нельзя просто взять связку из Python Requests и библиотеки BeautifulSoup, чтобы проанализировать содержимое и извлечь нужные данные. Сначала нужно выполнить все скрипты, отрендерить страницу и только потом смотреть на её HTML-код. Для этого нужен либо специальный JavaScript-движок, либо полноценный браузер (в браузерах такие движки уже встроены: V8 для Chromium-based и SpiderMonkey для Firefox).
Этот материал для тех, кто хочет узнать, как срейпить вебсайты с Python, если страницы имеют динамический контент: какие для этого потребуются инструменты и библиотеки, существуют ли сильные подходы и методы, на какие ошибки и подводные камни можно напороться.
Понимание динамического контента
Современный web сильно изменился. Когда-то всё начиналось с HTML-кода. Он давал в основном только возможность разметки, чтобы документы и web-страницы не выглядели убого. Браузеры получали HTML-контент, рендерили его и показывали пользователям.
Когда появились препроцессоры гипертекста (на базе полноценных языков программирования, таких как PHP), открылась возможность собирать web-страницы из разных блоков и элементов. Само содержимое стало храниться в специальных базах данных. По факту уже такие сайты стали называть «динамическими». За их работу стали отвечать CMS-системы, такие как WordPress, Joomla, Drupal и т.п.
Но и этого было мало. Оказалось, что динамическими могут быть не только сайты, но и контент. Особую популярность получил JavaScript, который отвечал за точечное изменение содержимого прямо в браузере — после взаимодействия с пользователем. Это была известная многим технология AJAX (она позволяла «перезагружать» и заново рендерить только отдельный блок или элемент — без перезагрузки всей страницы). Но в какой-то момент разработчики сайтов выяснили, что «монолит» из-за препроцессинга тратит слишком много времени и сил на то, чтобы подготовить браузеру результирующий HTML-код. И как не пытались они усилить серверы, их мощностей всё равно не хватало для того, чтобы справиться с потоком большого числа пользователей. Привет, Google, Amazon, Facebook!
Оригинальным ответом стали генераторы статических сайтов — на базе таких JavaScript-фреймворков, как React, Angular и Vue. Но пусть название вас не смущает. Сайты в них получаются отнюдь не статическими. Просто, теперь вместо того, чтобы каждый раз по требованию пользователя собирать результирующий HTML, сайты собираются непосредственно в браузерах. А так как JavaScript-скрипты относятся к категории «просто файлов» (то есть для сервера это статический контент), их можно раскидать по другим серверам, например, внутри распределённой CDN-сети. Тогда нагрузка на хостинг падает в разы. Плюс появляется возможность удобного децентрализованного хранения содержимого.
В итоге, когда клиент запрашивает конкретную страницу, его перенаправляют на ближайшее к нему CDN-хранилище, подтягивают из хранилища нужные JS-скрипты и собирают страницу внутри браузера.
Хитро? Да, если бы не проблемы со сбором данных с таких «динамических web-страниц». Ведь чтобы их распарсить, нужно сначала загрузить все JS-скрипты, выполнить их в специальной среде (JS-движок чем-то напоминает Java-машину) и только потом сформировать итоговый HTML-код. Проще прогнать сайт через реальный браузер. Примерно так и появилась потребность в headless-браузерах, а также в web-драйверах, которые могли организовать обмен данными с обычными браузерами (речь о таких библиотеках, как Selenium, Playwright, Puppeteer).
Различия между статическими и динамическими веб-страницами
Вообще, статические web-страницы — это чистый HTML-код. Такие страницы можно сохранить в виде обычных текстовых документов с расширением .html. Тогда они будут открываться в любом браузере.
Пример такой статической страницы:
<!DOCTYPE html>
<html>
<head>
<title>Это заголовок страницы, который отображается в результатах поисковой выдачи, а также при наведении курсора на вкладку браузера</title>
<meta charset="utf-8">
</head>
<body>
<h1>А это заголовок, который показывается в теле страницы</h1>
<p>
Это просто абзац, внутри может быть какой-то текст с описанием чего-то полезного…
</p>
<h2>Подзаголовок, пусть будет название списка «Типы прокси»:</h2>
<ul id="thebestlist">
<li>Резидентные прокси</li>
<li>Мобильные прокси</li>
<li>Общие прокси</li>
<li>Датацентровые прокси</li>
<li>Приватные прокси</li>
</ul>
</body>
</html>
Можете поэкспериментировать: создайте простой текстовый документ на своём ПК, скопируйте в него код из примера, и измените расширение с .txt на .html. Двойной клик по файлу должен открыть страницу в браузере.
Страницы, которые создаются с помощью PHP (или других гипертекстовых препроцессоров), сначала выполняются в специальной среде на сервере и только потом отдаются браузеру. Технически это тот же «статический контент». Так как браузер работает с простым HTML.
Динамические web-страницы выглядят немного иначе. Самый яркий пример динамической web-страницы в современном Интернете — выдача Google. От HTML там только <title>, <head> и <body>. И те используются в основном для вставки ссылок на JS-скрипты.
Попытка «увидеть» осмысленный контент или вёрстку ни к чему не приведёт. Более того, код намеренно минифицирован (из него удаляются пробелы, перекаты и другие спецсимволы, чтобы уменьшить размер кода).
По такому же принципу организованы многие другие крупные сайты и веб-сервисы, поэтому можно парсить TikTok, собирать данные с IMDb и изучать другие платформы..
Но самый распространённый формат динамического контента, на подавляющем большинстве сайтов и сервисов — это встраивание блоков или элементов с подгрузкой на основе действий пользователя. Это могут быть:
- Бесконечные ленты товаров и комментариев — они подгружаются по мере прокрутки страницы или при нажатии специальной кнопки (например, «Показать ещё»).
- Изображения и видео с «ленивой» подгрузкой (lazy-load) — используются для ускорения показа основного контента (все элементы вне главного фокуса будут подгружаться в фоне или по мере скроллинга).
- Настраиваемые фильтры — обычно присутствуют в каталогах товаров, но могут быть и в виде сложных виджетов с галереями изображений/медиаэлементов.
Резидентные прокси
Лучшие прокси-серверы для доступа к ценным данным со всего мира.
Проблемы парсинга динамического контента
Примерно здесь уже должна быть понятна вся трагедия web-скрапинга динамического контента. Но на всякий случай уточним детали:
- Нет явной HTML-структуры. Всё дело в том, что данные загружаются через JavaScript. Они нарезаны на куски и хранятся в разных скриптах (то тут, то там). Итоговый код собирается на основе определённой логики, в том числе на основе действий и поведения конечного пользователя. В качестве триггеров и условий может использоваться что угодно: разрешение экрана, версия браузера, определённая операционная система устройства и прочее. После срабатывания каждого такого триггера результирующий HTML-код меняется на новый (пересобирается заново).
- Чтобы получить итоговый HTML, нужен браузер. Да, с недавних пор официальный Chrome обзавёлся API (CDP-протокол), но даже для него нужна прослойка, чтобы организовать более читаемый и логичный синтаксис команд. По этой причине самым удобным форматом для взаимодействия с headless-браузерами и антидетектами остаётся использование web-драйверов. Для Python наиболее популярны: Selenium и Playwright. Для Java существует специальная реализация headless-браузера — HtmlUnit (это правильно упакованный JS-движок с готовым API-интерфейсом).
- JavaScript-код, исполняемый внутри браузера, открывает особые возможности для защиты сайтов от ботов. Соответственно, парсеры и headless-браузеры легко обнаружить и заблокировать. Например, может быть реализована проверка заголовков запроса (User-Agent, Referer и т. д.), анализ списка предустановленных шрифтов, версий процессора и видеокарты, содержимого cookies, localStorage и кеша, Canvas-отпечатков, поведения пользователя (изучается «человечность» перемещения по страницам, управления указателем мыши, скроллинг и т.п.).
- Обфускация и шифрование данных. Данные могут загружаться в зашифрованном виде и расшифровываться только на стороне браузера. Плюс могут использоваться динамически создаваемые токены аутентификации (например, CSRF-токены).
- Итоговый HTML-код надёжно защищается от парсинга. Так как контент формируется «в линию», то специальные скрипты могут использовать случайные комбинации из символов и цифр, чтобы подмешивать их в названия классов и идентификаторов, в состав дополнительным параметров и атрибутов. В итоге получается, что каждая новая страница имеет уникальную структуру. То есть вы не можете один раз определить конкретный идентификатор, чтобы скрипт парсера искал его на всех аналогичных страницах. Для большего понимания можете изучить пример того, как выглядит HTML-код блока выдачи Google People Also Ask.
Выбор правильных инструментов для парсинга динамического контента
Так как вебстраницы с динамическим контентом нужно предварительно рендерить, то для парсинга на языке Python над могут потребоваться следующие инструменты и библиотеки:
- Selenium — один из лучших web-драйверов для автоматизации Headless-браузеров, разрабатывается независимой командой. Легко интегрируется с антидетектами. Много чего умеет, вплоть до оркестрации облачных инстансов, поддерживает разные платформы и языки программирования, включая Python. К слову, Headless-браузеры Selenium устанавливает и подключает автоматически.
- Playwright — ещё один web-драйвер, тоже поддерживает разные языки программирования, браузеры и платформы. Разрабатывается и сопровождается компанией Microsoft. Не менее популярный и функциональный, если сравнивать с Selenium. Самая важная фишка — единый стандартизированный синтаксис. Он одинаковый для всех подключаемых браузеров и языков программирования, максимально удобный и понятный. О применении Playwright в парсинге.
- BeautifullSoup — библиотека для синтаксического анализа HTML. Разбирает код страницы на составляющие и ищет нужные элементы. Собственно, именно BS4 отвечает непосредственно за парсинг (за выборку данных). Подробный материал об использовании BeautifullSoup с примерами.
- Антидетект-браузеры — нужны для удобной работы с большим количеством цифровых отпечатков (это как удобная ферма изолированных браузеров и профилей к ним). Сравнение популярных антидетектов.
- Ротируемые прокси — обеспечивают защиту персональных данных и эмулируют расположение пользователя. Ротируемые прокси проще подключаются к профильному софту и легко настраиваются (по API или в личном кабинете — с определением логики ротации, типа IP-адресов, расположения и т.п.).
Это стандартный джентльменский набор для веб-скрапинга динамического контента. При желании можете посмотреть дополнительные библиотеки для парсинга на Python.
Остальное будет выявляться по мере необходимости: инструменты для реализации случайных задержек и асинхронных запросов, скрипты для продвинутой ротации прокси, библиотеки удобного форматирования данных и т.п.
Headless-браузеры в списке намеренно не указали, так как сейчас в большинстве случаев они устанавливаются пакетом вместе с web-драйверами.
Методы парсинга динамических веб-сайтов на Python
Общий алгоритм web скрапинга динамического контента выглядит следующим образом:
- Вы устанавливаете и настраиваете web-драйвер, а также комплект headless-браузеров к нему. Под настройкой браузера понимается формирование уникального цифрового отпечатка — набор кук, истории просмотров, установка браузерных расширений, включение стелс-режима. «Стелс-режим» нужен для того, чтобы скрыть следы использования headless-браузера.
- Далее к вашему Python-скрипту подключается библиотека web-драйвера. С помощью специальных команд (в соответствии с API выбранного драйвера) headless-браузеру передаётся список целевых страниц с динамическим контентом.
- Браузер их открывает и обсчитывает (точно также, как это делается для показа обычным пользователям). При необходимости в скрипте парсинга могут быть заданы дополнительные действия: прокрутка динамических web-страниц, эмулирование нажатий на кнопки и т.п.
- Результирующий HTML-код страницы передаётся на извлечение в библиотеку синтаксического анализатора. То есть происходит стандартная процедура парсинга, как если бы скрипт изначально работал с чистым HTML-кодом. В некоторых web-драйверах имеются встроенные инструменты синтаксического анализа, но тут уже дело вкуса и привычки — кому какой вариант выборки удобнее.
- Полученные данные сохраняются в нужном формате. Например, передаются по API, сохраняются в файл, отправляются напрямую в базу данных и т.п.
Ниже подробнее погрузимся в технические детали парсинга динамических web-страниц.
Использование сетевых запросов (парсинг по API)
Отдельно стоит оговорить возможность обмена данными с целевыми сайтами по API. Некоторые крупные площадки знают, что информация с их страниц участвует в разных бизнес-процессах. И специально для того, чтобы разгрузить свой основной сайт (web-версию интерфейса), для парсеров предоставляется отдельный интерфейс — программный (API, то есть Application Programming Interface, русск. «Программный Интерфейс Приложения»).
API можно найти, например, у таких площадок, как Amazon (платный, с небольшим объёмом бесплатных обращений), IMDb (платный, формат GraphQL), TikTok (бесплатно, в том числе для бизнес-задач).
Соответственно, вы можете написать свой Python-скрипт, который будет формировать запросы к нужному API-ресурсу и разбирать полученные ответы на составляющие. Обычно данные поставляются в структурированном виде, поэтому разобрать их на отдельные фрагменты будет несложно. Никакие headless-браузеры и web-драйверы для этого не нужны. Достаточно простейших библиотек для работы с HTTP-запросами, например, Python Requests (тут даже есть встроенная поддержка разбора ответов в формате JSON).
Основная проблема — у каждого сайта обычно свой API. То есть свой набор команд и формат ответов. Плюс для подключения обязательно нужно зарегистрировать аккаунт разработчика и получить API-ключ (он выступает в роли идентификатора). На каждый аккаунт обычно выставляются специальные технические лимиты. Тут стоит сказать, что, если вы превысите ограничения, никаких серьёзных санкций за это не применят. Максимум вместо ответа будут возвращать ошибку. Как только лимит сбросится, вы снова сможете обмениваться данными.
Многие крупные сервисы, как поиск Google, видеохостинг YouTube и т.п., не имеют API. Но их данные крайне важны и могут участвовать в комплексной аналитике.
Вместо этого вы можете использовать сервисы-посредники, которые реализуют недостающий API. Но они как правило платные.
Схема работы таких сервисов выглядит примерно так:
- Вы отправляете запрос не на прямую к целевому сайту, а к сервису, реализующему API-интерфейс.
- Они получают ваш запрос и самостоятельно парсят данные на целевом сайте (или извлекают их из своих баз данных, по аналогии с кешированием).
- Вам в ответ возвращаются готовые структурированные данные. Поэтому, опять же, не нужно устанавливать никаких headless-браузеров или web-драйверов для рендеринга динамического контента.
- Останется только обработать полученную информацию или оставить её «как есть».
Пример такого сервиса — Froxy Web Scraper. Вы можете работать со многими крупными площадками, такими как Google, Ask, Yahoo, Bing, YouTube, Amazon, AliExpress, Ebay, Wildberries, Pinterest и прочими. Для нестандартных задач есть опция получения результирующего HTML-кода по списку целевых страниц. Для каждого задания на парсинг можно установить периодичность и параметры таргетинга (местоположение прокси, вплоть до города и оператора связи, а также тип адреса: мобильный или резидентный). По окончании парсинга сервис может отправлять вызовы на указанные веб-хуки — чтобы ваш скрипт мог начать скачивание результатов и пустить их в дальнейшую обработку.
Пример работы в материале про парсинг Google SERP.
Глобальное покрытие
Получите доступ к прокси-сети с 200+ локациями и 10+ миллионами IP-адресов.
Использование Selenium для автоматизации браузера
Установка библиотеки выполняется командой:
pip install selenium
PIP — это штатный менеджер пакетов для Python. Обратите внимание, что для успешного выполнения ссылка в Windows на исполняемый файл pip.exe должна быть добавлена в переменные окружения.
WebDriver и пакет совместимых headless-браузеров будут установлены автоматически. Это займёт некоторое время, поэтому обязательно дождитесь успешного завершения процесса установки.
Пример простейшего скрипта, который будет обращаться к специальному тестовому сайту (scrapingcourse.com/javascript-rendering) и собирать список товаров:
import time
import csv
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
# Настройки Selenium
driver = webdriver.Chrome() # Запуск браузера
driver.get("https://www.scrapingcourse.com/javascript-rendering") # Открываем целевой сайт
time.sleep(5) # Ждём загрузки результатов
# Получение результатов поиска
results = driver.find_elements(By.CSS_SELECTOR, "div.product-item") # Контейнеры с товарами
data = [] # Список для хранения данных
for result in results:
try:
title = result.find_element(By.CSS_SELECTOR, "span.product-name").text # Заголовок
link = result.find_element(By.TAG_NAME, "a").get_attribute("href") # Ссылка
price = result.find_element(By.CSS_SELECTOR, "span.product-price").text # Цена
data.append([title, link, price]) # Добавляем в список
except Exception as e:
print(f"Ошибка при парсинге: {e}")
# Закрытие браузера
driver.quit()
# Сохранение в CSV
csv_filename = "products_results.csv"
with open(csv_filename, "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(["Заголовок", "Ссылка", "Цена"])
writer.writerows(data)
# Вывод данных в консоль
for item in data:
print(item)
Создайте текстовый файл с именем «web_scraping_dynamic_content_with_selenium». Замените расширение с .txt на .py. Откройте его в любом текстовом редакторе, скопируйте и вставьте код, приведённый выше. Сохраните изменения.
Теперь осталось запустить скрипт (команда для консоли,:
python web_scraping_dynamic_content_with_selenium.py
Если вы не переходили в каталог расположения скрипта, то укажите полный путь к файлу, например:
python C:\my-scraper\web_scraping_dynamic_content_with_selenium.py
После запуска и закрытия окна браузера (подождите 5 секунд) в консоли должен вывестись список товаров с ценами и ссылками. А рядом со скриптом (в той же папке) появится CSV-файл с тем же содержимым, но уже в готовом табличном виде.
Использование Playwright для парсинга динамического контента
Установка библиотеки выполняется несколькими командами. Первая ставит саму библиотеку и зависимости к ней, вторая — доустанавливает все необходимые браузеры:
pip install playwright
playwright install
Если вам нужен конкретный браузер, то его нужно указать в качестве дополнительного аргумента. Например, так:
playwright install chromium
Пример простейшего скрипта, который работает точно также, как и скрипт для Selenium. То есть обращается к тестовому динамическому web-сайту (scrapingcourse.com/javascript-rendering) и собирает с него список товаров:
import csv
from playwright.sync_api import sync_playwright
# Логика парсинга Товаров
with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=False) # Запуск браузера без фонового режима
page = browser.new_page()
# Открываем целевой сайт
page.goto("https://www.scrapingcourse.com/javascript-rendering")
# Ждем загрузки результатов. Здесь указываем конкретный селектор (вместо ожидания по времени, дефолтный таймаут 30 секунд, на случай, если выбранный селектор не будет найден)
page.wait_for_selector("div.product-item")
# Собираем результаты
results = page.locator("div.product-item").all()
data = []
for result in results:
try:
title = result.locator("span.product-name").inner_text() # Заголовок
link = result.locator("a").get_attribute("href") # Ссылка
price = result.locator("span.product-price").inner_text() # Цена
data.append([title, link, price])
except Exception as e:
print(f"Ошибка при парсинге: {e}")
browser.close() # Закрываем браузер
# Сохранение в CSV
csv_filename = "results_playwright.csv"
with open(csv_filename, "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(["Заголовок", "Ссылка", "Цена"])
writer.writerows(data)
# Вывод данных в консоль
for item in data:
print(item)
Создайте текстовый файл с именем «web_scraping_dynamic_content_with_playwright». Скопируйте код, представленный выше, и сохраните файл. Закройте и смените расширение с .txt на .py.
Запустите скрипт в консоли:
python web_scraping_dynamic_content_with_playwright.py
Вывод будет аналогичным первому скрипту. В каталоге со скриптом должен появиться файл results_playwright.csv (с таблицей товаров, ссылок и цен).
Что ещё может потребоваться для скрапинга динамического контента
Во-первых, нужно детально изучить и понять, как работают синтаксические анализаторы. Это краеугольный камень для качественного парсинга — они нужны для поиска и перемещения по DOM-структуре (по схеме HTML-разметки).
Без понимания того, что и где нужно искать, вы не сможете «объяснить» ничего своей программе.
Разные web-драйвера используют разные синтаксисы для поиска элементов и для сбора из них данных.
Например, Playwright работает с локаторами (это внутренний механизм), а также поддерживает синтаксис XPath и CSS. Если этого недостаточно, вы можете доустановить библиотеки других синтаксических анализаторов, таких как BeautifullSoup (тут будет уже другой синтаксис и порядок поиска по DOM-структуре).
У Selenium немного другой подход, но он во-многом похож на работу Playwright. Внутренний инструмент «By» может искать элементы на основе имён, идентификаторов, тегов, классов, CSS-селекторов и XPath-синтаксиса. Опять же, никто не запрещает вам передавать результирующий HTML в другой синтаксический анализатор.
Во-вторых, крупные web-сервисы с динамическим содержимым давно научились определять headless-браузеры по цифровым отпечаткам. Чтобы избежать блокировок и показа капчи, нужно скрывать эти следы и стараться работать через прокси (так как каждый новый IP-адрес прокси — это новый клиент, который изучается и блокируется системами безопасности в индивидуальном порядке).
Прокси к headless-браузерам обычно подключается на уровне отдельно взятого экземпляра. Поэтому, чтобы не придумывать костыли с ротацией прокси и запуском новых браузеров (ведь память вашего устройства не бесконечная), логично изначально подключать ротируемые прокси.
Заключение и рекомендации
Современные сайты сильно усложнились. Теперь это не просто HTML-страницы, а полноценные web-приложения. Работать с динамическим содержимым стало заметно труднее, так как вы не можете напрямую получить DOM-структуру и выбрать нужные данные из HTML. Вместо этого вам нужно загружать сайт или динамические страницы в настоящем браузере (его роль могут выполнять headless-браузеры или антидетекты) и только потом приступать к парсингу.
Несмотря на то, вы работаете с контентом через браузеры, вам всё равно придётся продумать массу технических деталей, среди которых эмулирование цифровых отпечатков, имитация поведения пользователя и работа через прокси.
Найти качественные прокси можно у нас. Froxy — это 10+ млн. IP (резидентные, мобильные и серверные прокси с таргетингом до города и оператора связи).
Если вам не хочется усложнять свой Python-скрапер, всегда можно задействовать парсинг по API. Froxy сможет помочь и здесь. Вы можете отправить задание на сбор данных по конкретным сайтам. Мы всё сделаем сами и отдадим вам готовую структурированную информацию в JSON или CSV-формате. Никаких прокси и headless-браузеров настраивать не придётся. Подробности — в описании услуги скрапер Froxy.