Как парсить сайты, которые рендерятся на JavaScript
Многие современные сайты присылают почти пустой HTML-документ и строят содержимое в браузере с помощью JavaScript. Если ваш парсер загружает страницу и не находит ни товаров, ни цен, ни текста статьи, содержимое есть, но приходит позже. Это руководство показывает, как понять, с каким типом страницы вы имеете дело, как найти более быстрый путь, чем браузер, где он существует, и как парсить отрисованные страницы с помощью Playwright на Python. В конце — заметка о том, что делать, если отрисованная страница показывает капчу.
Шаг 1: проверьте, есть ли содержимое в HTML
Откройте страницу через view-source: в браузере или загрузите ее простым HTTP-запросом и поищите в исходнике фрагмент текста, который видите на экране. Встречаются три варианта:
- Текст есть. Страница отрисована на сервере. Достаточно простого HTTP-клиента и HTML-парсера, и их стоит использовать, потому что они быстрее и проще.
- Текста нет, но есть JSON-блок. Многие сайты встраивают свое состояние в тег script, например
__NEXT_DATA__. Разбирайте этот JSON напрямую. - Текста нет и блока нет. Страница подгружает содержимое дополнительными запросами. Посмотрите вкладку Network в DevTools с фильтром Fetch/XHR, пока страница загружается.
Шаг 2: вызовите JSON-эндпоинт, если он есть
Когда страница получает данные с эндпоинта, обычно лучше всего вызвать этот эндпоинт напрямую. Он меньше, быстрее и стабильнее отрисованной страницы. Скопируйте URL запроса, проверьте, какие заголовки требует сайт, и попробуйте из скрипта. Делайте это только там, где условия сайта разрешают, и соблюдайте любые ограничения частоты или аутентификацию, которых ожидает сайт. Недокументированный эндпоинт может измениться без предупреждения, поэтому пишите парсер так, чтобы он громко падал, когда структура ответа меняется.
Шаг 3: отрисовывайте страницу, когда это необходимо
Если содержимое действительно требует браузера, запустите headless-браузер. Playwright — хороший выбор для Python. Пример ниже открывает список товаров, ждет появления содержимого и читает текст из каждой карточки:
from playwright.sync_api import sync_playwright
URL = "https://example.com/products" # страница, парсинг которой разрешен
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(URL, wait_until="domcontentloaded", timeout=60_000)
# Ждем элемент, который подтверждает, что данные загрузились.
page.wait_for_selector(".product-card", timeout=30_000)
titles = page.locator(".product-card h2").all_inner_texts()
browser.close()
for title in titles:
print(title)
Ожидание конкретного селектора намного надежнее фиксированного sleep. Фиксированная пауза либо слишком короткая в медленный день, либо тратит время в быстрый. Выберите элемент, который появляется только после загрузки данных.
Шаг 4: обрабатывайте бесконечную прокрутку и пагинацию
Некоторые страницы подгружают больше элементов при прокрутке. Прокрутите вниз, дождитесь роста числа элементов и остановитесь, когда оно перестанет расти. Страницы с нумерованной пагинацией проще: найдите ссылку на следующую страницу, перейдите по ней и остановитесь на последней. Всегда задавайте максимальное число страниц или прокруток, чтобы ошибка не заставила краулер работать вечно.
def scroll_until_done(page, selector: str, max_rounds: int = 20) -> int:
previous = -1
for _ in range(max_rounds):
count = page.locator(selector).count()
if count == previous:
break
previous = count
page.mouse.wheel(0, 4000)
page.wait_for_timeout(1500) # даем следующей порции время загрузиться
return previous
Шаг 5: держите браузер экономным
Отрисовка дорогая. Блокируйте изображения, шрифты и медиа, которые вам не нужны, и переиспользуйте один браузер для многих страниц вместо запуска нового каждый раз. Закрывайте страницы и контексты, когда закончите, чтобы память не росла на протяжении долгого запуска.
def block_heavy_assets(route):
if route.request.resource_type in ("image", "font", "media"):
route.abort()
else:
route.continue_()
page.route("**/*", block_heavy_assets)
Шаг 6: когда отрисованная страница показывает капчу
Иногда отрисованная страница показывает капчу до того, как показать какое-либо содержимое. Если эта капча — PopularCaptcha, живой тип капчи в Scarnox, вы читаете sitekey со страницы, запрашиваете токен и помещаете его в поле формы, которое ожидает сайт. Руководство по PopularCaptcha на Python проходит этот шаг. Другие виджеты пока не работают в режиме онлайн, поэтому проверьте типы капчи, прежде чем что-то планировать.
Будьте внимательны к самому браузеру. Headless-браузер, который явно выглядит автоматизированным, может вызвать больше проверок, а не меньше. Прочитайте обнаружение headless-браузера, чтобы понять, на какие сигналы обращать внимание, и предпочитайте более медленные загрузки страниц и их меньшее количество.
Ответственный парсинг
- Проверьте
robots.txtи условия сайта, прежде чем отрисовывать его страницы или вызывать его эндпоинты. - Держите ограничения частоты низкими и кешируйте результаты, чтобы загрузить каждую страницу один раз.
- Не собирайте персональные данные без законного основания.
- Если сайт ограничивает доступ, остановитесь. Не пытайтесь обойти ограничение.
Следующие шаги
- Парсинг без блокировок
- Обнаружение headless-браузера
- Парсинг сайтов за Cloudflare
- Обработка капчи в Playwright и Puppeteer
Начните с бесплатного пробного баланса, карта не нужна: создайте аккаунт и протестируйте поток PopularCaptcha на странице, которой вы владеете. Актуальные тарифы — на странице цен.