Парсинг динамических сайтов на Python с помощью Playwright и BeautifulSoup

🕷️ Кратко: Обычный requests не видит контент, загружаемый через JavaScript. Решение — Playwright для рендеринга страницы + BeautifulSoup для удобного парсинга.
python парсинг js сайта

Пару месяцев назад я пытался собрать список новостей с сайта на Vue.js.
Сначала написал простой скрипт на requests + BeautifulSoup — получил пустую страницу с одним <div id="app"></div>.
Потом подумал: «Ладно, добавлю time.sleep() и Selenium» — но это оказалось медленным и нестабильным.

А потом я попробовал Playwright — и всё заработало с первого раза, без костылей.

Если вы парсите современные сайты (React, Vue, Angular, SPA), ваш код должен уметь «ждать» JavaScript. В этой статье я покажу, как сделать это без боли, с живыми примерами и готовыми решениями.

Почему requests и BeautifulSoup не работают с динамическими сайтами?

Когда вы открываете сайт в браузере, происходит следующее:

  1. Сервер отдаёт базовый HTML (часто почти пустой)
  2. Браузер загружает JavaScript
  3. JS делает API-запросы и динамически вставляет контент в DOM

Пример:

<!-- То, что получает requests -->
<div id="root"></div>

<!-- То, что видит пользователь (и Playwright) -->
<div id="root">
  <article>Новость 1</article>
  <article>Новость 2</article>
</div>

Модуль requests не выполняет JavaScript, поэтому вы получаете «скелет».
Решение — использовать настоящий браузер, который прогонит JS и даст финальный HTML.

Почему Playwright + BeautifulSoup — идеальная связка?

  • Playwright — быстрый, современный, с встроенными ожиданиями (wait_for_selector)
  • BeautifulSoup — удобный API для навигации по DOM, CSS-селекторы, обработка текста
  • Вместе они дают мощность браузера + гибкость парсинга

📚 Документация:

Установка и настройка

pip install playwright beautifulsoup4
playwright install chromium

💡 Playwright сам скачивает легковесную версию Chromium — не нужно устанавливать браузер вручную.

Пример 1. Парсим цитаты с JS-сайта

Выбор демо-сайта

Используем официальный тестовый сайт:
👉 https://quotes.toscrape.com/js/

Он полностью построен на JavaScriptrequests вернёт пустой список.

Полный рабочий скрипт

from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
import time

url = "https://quotes.toscrape.com/js/"

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    
    print("Переходим на страницу...")
    page.goto(url)
    
    # Ждём появления хотя бы одной цитаты
    print("Ждём загрузки контента...")
    page.wait_for_selector(".quote", timeout=10000)
    
    # Получаем финальный HTML после выполнения JS
    html = page.content()
    browser.close()

    # Парсим через BeautifulSoup
    soup = BeautifulSoup(html, "html.parser")
    quotes = soup.select(".quote")

    print(f"\nНайдено цитат: {len(quotes)}\n")
    
    for i, quote in enumerate(quotes[:5], 1):  # первые 5
        text = quote.select_one(".text").get_text(strip=True)
        author = quote.select_one(".author").get_text(strip=True)
        tags = [tag.get_text() for tag in quote.select(".tag")]
        print(f"{i}. «{text}» — {author}")
        print(f"   Теги: {', '.join(tags)}\n")

Вывод:

1. «The world as we have created it is a process of our thinking.» — Albert Einstein
   Теги: change, deep-thoughts, thinking, world

2. «It is our choices, Harry, that show what we truly are, far more than our abilities.» — J.K. Rowling
   Теги: abilities, choices
...

✅ Контент получен! Даже теги — всё на месте.

Пример 2. Парсим таблицу с динамической подгрузкой

Допустим, нужно спарсить таблицу, которая появляется после клика.

Сайт: https://demo.scrapingbee.com/dynamic-table

from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto("https://demo.scrapingbee.com/dynamic-table")
    
    # Кликаем по кнопке "Load Table"
    page.click("button:has-text('Load Table')")
    
    # Ждём появления таблицы
    page.wait_for_selector("table tbody tr", timeout=10000)
    
    html = page.content()
    browser.close()

    soup = BeautifulSoup(html, "html.parser")
    rows = soup.select("table tbody tr")

    for row in rows[:3]:
        cells = [td.get_text(strip=True) for td in row.find_all("td")]
        print(cells)

Этот подход работает даже если контент подгружается по AJAX после клика.

Как правильно ждать? Советы по надёжности

Используйте wait_for_selector вместо time.sleep()

❌ Плохо:

time.sleep(3)  # Может не хватить или ждать лишнего

✅ Хорошо:

page.wait_for_selector(".result-item", timeout=15000)

Ожидание «тишины» в сети

page.goto(url, wait_until="networkidle")  # Ждать, пока не прекратятся запросы

Обработка ошибок

try:
    page.wait_for_selector(".quote", timeout=10000)
except TimeoutError:
    print("&#x274c; Контент не загрузился — возможно, сайт изменился")
    return

Сохранение результата в CSV

import csv

with open("quotes.csv", "w", encoding="utf-8", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["Цитата", "Автор", "Теги"])
    
    for quote in quotes:
        text = quote.select_one(".text").get_text(strip=True)
        author = quote.select_one(".author").get_text(strip=True)
        tags = ", ".join(tag.get_text() for tag in quote.select(".tag"))
        writer.writerow([text, author, tags])

print("&#x2705; Результат сохранён в quotes.csv")

Заключение

Теперь вы умеете:

✅ Парсить сайты, где контент грузится через JavaScript
✅ Дожидаться нужных элементов без time.sleep()
✅ Объединять мощь Playwright и удобство BeautifulSoup
✅ Сохранять результат в структурированном виде

Эта связка — стандарт де-факто для современного парсинга на Python.
Даже если сайт использует сложную логику на React или Vue — вы получите нужные данные.

🐍 Попробуйте запустить примеры выше — и вы убедитесь, насколько это надёжно и просто.

💬 Остались вопросы? Пишите в комментариях — с радостью уточню, дополню или помогу с вашим кодом.
📢 Подписывайтесь на Telegram-канал PythonAuto, чтобы не пропустить новые гайды по автоматизации, парсингу и Python.
👉 Ваш интерес — лучшая мотивация для новых статей!

Оставьте комментарий