Урок курса

Браузерные агенты (Playwright)

AI Agents PRO: LangGraph, AutoGen и LLMOps в продакшне

Введение в Playwright

Представьте, что вам нужно протестировать веб-приложение: зайти на сайт, заполнить форму, нажать кнопку, проверить результат. Делать это вручную каждый раз — утомительно и медленно. Или другая задача: собрать данные с сайта, который загружает контент через JavaScript. Обычные HTTP-запросы не помогут — нужен настоящий браузер.

Playwright — это библиотека для автоматизации браузеров, позволяющая управлять Chromium, Firefox и WebKit из Python. Как правило, её используют для автоматизации тестов веб-приложений, скрапинга данных и симуляции действий пользователя. Основное преимущество Playwright в том, что она поддерживает асинхронное выполнение, надежно работает с динамическими страницами и предоставляет мощные инструменты для работы с селекторами и ожиданиями.

Удобные функции этого фреймворка:

  • Поддерживает Chromium, Firefox и WebKit — можно тестировать сайты под разными браузерами.
  • Полностью работает с асинхронными страницами (SPA, динамический контент).
  • Позволяет делать скриншоты и видео для диагностики тестов.
  • Есть синхронное и асинхронное API, что удобно для разных сценариев.

Запуск браузера

Для начала работы с Playwright необходимо установить библиотеку. Это можно сделать с помощью команды:

pip install playwright

После установки необходимо инициализировать браузеры (скачать бинарные файлы Chromium, Firefox и WebKit):

python -m playwright install

Теперь можно написать код для запуска браузера. Пример кода для открытия страницы выглядит следующим образом:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)  # Запуск браузера Chromium
    page = browser.new_page()  # Создание новой страницы
    page.goto("https://example.com")  # Переход на сайт
    print(page.title())  # Вывод заголовка страницы
    browser.close()  # Закрытие браузера

В данном примере используется синхронный API Playwright. Браузер запускается, открывается новая страница, осуществляется переход на указанный URL, и выводится заголовок страницы.

Пояснения:

  • headless=True — браузер работает без графического интерфейса (по умолчанию). Удобно для серверов или CI/CD.
  • headless=False — можно визуально наблюдать за действиями браузера. Полезно при отладке.
  • page.goto(url) — открывает страницу. Можно указать таймаут: page.goto(url, timeout=10000).

Работа с селекторами — как найти элементы на странице

После загрузки страницы нужно найти конкретные элементы: кнопку для клика, поле для ввода текста, заголовок для проверки. Для этого используются селекторы — способы описать, какой именно элемент нас интересует.

Селекторы позволяют находить элементы на веб-странице. Playwright поддерживает несколько типов селекторов:

CSS-селекторы:

page.locator("button.submit")

Найдет все кнопки с классом submit.

XPath:

page.locator("//button[@id='submit']")

Позволяет точнее находить элементы, особенно в сложной структуре HTML. В этом примере:

  • // — область поиска по всему документу. Не важно, где находится элемент: в <body>, внутри <div> или глубоко вложен, в отличие от /, который задаёт жёсткий путь от корня.
  • button — тип узла. Ищем HTML-элемент <button>. Другие элементы (input, a, div) не подойдут.
  • [@id='submit'] — фильтр. Квадратные скобки — условие отбора. @id — обращение к атрибуту id. 'submit' — точное значение.
  • В итоге: выбрать со всей страницы только те <button>, у которых id="submit".

Текстовые селекторы:

page.locator("text=Отправить")

Ищет элемент по тексту.

Лучше использовать locator, а не старый query_selector, потому что локатор поддерживает встроенные ожидания и автоматически ждёт появления и готовности элементов для взаимодействия.

Ожидания — работа с динамическим контентом

Современные сайты часто загружают контент динамически: кнопка появляется после загрузки JavaScript, данные подгружаются через API. Если попытаться кликнуть на элемент, которого ещё нет на странице, получим ошибку. Для этого нужны ожидания.

Ожидания необходимы для корректного взаимодействия с элементами, которые могут загружаться асинхронно. В Playwright есть два основных способа ожиданий:

  1. Встроенные ожидания локаторов
    Методы локатора (click(), fill(), inner_text() и другие) автоматически дожидаются, пока элемент:

    • появится в DOM (Document Object Model),
    • станет видимым,
    • станет доступным для взаимодействия.

    Пример:

    heading = page.locator("h1") # ждёт, пока h1 станет доступен 
    print(heading.inner_text()) 
    
    heading.click(timeout=3000) # тоже ждёт готовности элемента

    Здесь нет необходимости явно вызывать wait_for_selector() — Playwright всё делает сам.

  2. Ожидание навигации
    Иногда требуется дождаться перехода на другую страницу после действия пользователя (например, после клика по ссылке):

    page.locator("a#next").click() # Клик по ссылке 
    page.wait_for_navigation() # Явное ожидание завершения перехода

Отличие от старого подхода

При использовании query_selector методы не ждут элемент, поэтому часто нужно делать:

page.wait_for_selector("h1", timeout=5000)  # ждёт до 5 секунд
element = page.query_selector("h1") 
print(element.inner_text())

Локаторы делают это автоматически, упрощая код и снижая вероятность ошибок из-за асинхронной загрузки контента.

Параметр wait_until — когда страница считается загруженной

Когда мы вызываем page.goto(), возникает вопрос: когда считать, что страница загружена? Когда HTML получен? Когда все картинки скачаны? Когда JavaScript выполнился?

wait_until определяет, когда Playwright считает, что переход на страницу завершён.

page.goto("https://example.com", wait_until="load")

Варианты:

  • load — происходит, когда всё: DOM + ресурсы (картинки, CSS, JS) загружено. Это классическое поведение по умолчанию.
  • commit — срабатывает сразу после того, как браузер получил ответ на основной запрос и начал загрузку документа. Не ждёт CSS, JS или изображений. Полезно, если нужно моментально начать работу с DOM, не дожидаясь полной загрузки.
  • domcontentloaded — срабатывает, когда HTML-документ полностью загружен и распарсен, но ресурсы (картинки, стили, шрифты) могут ещё загружаться. Полезно для SPA, где JS сам подгружает данные, или скриптов, которые работают с DOM сразу после загрузки.
  • networkidle (устаревший) — считается завершённым, когда нет сетевых соединений ≥ 500 мс. Раньше использовался для SPA, чтобы ждать подгрузки API-запросов. Сейчас Playwright не рекомендует использовать, так как это часто непредсказуемо — иногда сетевые пулы остаются открытыми. Рекомендуется ждать конкретный элемент на странице через селектор или ответ от сети через wait_for_response, чтобы точно определить готовность нужного контента.

Ретраи (Retries) — устойчивость к временным сбоям

Даже стабильные сайты иногда тормозят или недоступны из-за сетевых проблем. Вместо того чтобы падать с ошибкой, лучше повторить попытку.

Иногда сайты временно недоступны, и лучше повторить действие, чем падать:

from playwright.sync_api import TimeoutError

for attempt in range(3):
    try:
        page.goto("https://news.ycombinator.com/")
        break  # если успешно — выход из цикла
    except TimeoutError:
        print(f"Попытка {attempt + 1} не удалась. Повтор...")
else:
    print("Не удалось загрузить страницу после 3 попыток")
  • Повторные попытки позволяют обрабатывать временные сбои сети или замедленную загрузку страниц.
  • Комбинируется с ожиданиями для устойчивой работы.

Ошибки и крайние случаи

При работе с Playwright могут возникать различные ошибки:

  • Элемент не найден: Если указанный селектор не соответствует ни одному элементу на странице, будет выброшено исключение.
  • Таймаут ожидания: Если элемент не появился в течение заданного времени, будет выброшено исключение о таймауте.
  • Проблемы с навигацией: Если переход на новую страницу не завершился, это может привести к ошибкам.

Даже на стабильных сайтах могут быть неожиданные изменения (редизайн, обновление структуры), поэтому блоки try/except обязательны:

try:
    page.locator("button.submit").click()
except Exception as e:
    print("Не удалось нажать кнопку:", e)

Рекомендации:

  • Логируйте ошибки с помощью print или logging.
  • Используйте скриншоты при ошибках для понимания, что пошло не так:
    page.screenshot(path="error.png")
    

Работа с несколькими страницами и вкладками

Иногда нужно работать с несколькими сайтами одновременно или открыть ссылку в новой вкладке. Playwright позволяет открывать несколько вкладок и работать с ними независимо:

page1 = browser.new_page()
page1.goto("https://example.com")

page2 = browser.new_page()
page2.goto("https://example.org")
  • Каждая вкладка — отдельный контекст для действий.
  • Можно параллельно взаимодействовать с несколькими страницами.

Асинхронное API

Для высокой производительности (например, одновременная обработка десятков страниц) Playwright полностью поддерживает async/await. Пример:

import asyncio
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        await page.goto("https://example.com")

        # Клик по ссылке
        await page.locator("text=Learn more").click()

        # Получаем весь текст страницы
        page_text = await page.inner_text("body")
        print(page_text)

        await browser.close()

asyncio.run(main())

Артефакты: скриншоты и видео

Для диагностики проблем (особенно в CI/CD, где нет возможности визуально наблюдать) полезно сохранять визуальные артефакты:

1. Скриншоты:

page.screenshot(path="screenshot.png")

2. Запись видео сессии:

context = browser.new_context(record_video_dir="videos/")
page = context.new_page()
page.goto("https://example.com/")
# По окончании:
context.close()  # видео сохраняется в папку videos/

Итоги

Playwright позволяет полностью управлять браузером, поддерживает синхронное и асинхронное взаимодействие с элементами. С помощью селекторов и ожиданий можно надежно находить и взаимодействовать с элементами страницы, даже если они загружаются динамически. Встроенные механизмы ожиданий, ретраи и возможность записи артефактов делают автоматизацию браузера стабильной и удобной для отладки.

Дальше по курсу

Разберём как изолировать процессы, а также работать с контекстом выполнения (куки, локальное хранилище).

Изоляция: sandbox, контекст, download/upload

При автоматизации браузера с помощью Playwright тесты или действия агентов могут влиять друг на друга. Например:

  • Куки или локальное хранилище могут сохранять данные между запусками, вызывая неожиданные результаты.

  • Несколько тестов могут изменять состояние одной и той же страницы.

  • Скрипты одного агента могут «засорять» браузер или вкладки для других задач.

Представьте: вы тестируете процесс авторизации. Первый тест успешно логинится и сохраняет куки. Второй тест должен проверить поведение для незалогиненного пользователя, но браузер «помнит» сессию из первого теста, и проверка проваливается. Или два агента работают одновременно, и данные одного попадают в контекст другого.

Изоляция позволяет запускать каждый тест или действие в отдельной песочнице, чтобы изменения одного процесса не влияли на другие. Давайте разберёмся, как это работает в Playwright.

Контекст браузера (Browser Context) — изоляция сессий

Playwright поддерживает Browser Contexts — это отдельные окружения внутри одного браузера. Каждый контекст имеет свои куки, локальное хранилище, кэш и разрешения. Это похоже на режим инкогнито: когда вы закрываете окно, всё состояние стирается.

Важно понимать: контекст может содержать несколько страниц (вкладок), и все страницы внутри одного контекста разделяют состояние — куки, авторизацию, локальное хранилище. Если вам нужна полная изоляция между страницами, создавайте отдельные контексты.

Пример:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)  # чтобы видеть результат

    # Контекст для первого пользователя
    context1 = browser.new_context()
    page1 = context1.new_page()
    page1.goto("https://www.google.com")
    
    # Закрываем всплывающее окно (если оно есть — может не появиться у всех)
    # Проверяем наличие кнопки перед кликом
    if page1.locator("button:has-text('Не интересует')").count() > 0:
        page1.locator("button:has-text('Не интересует')").click()
    
    # Используем textarea для ввода запроса и нажимаем Enter
    page1.fill("textarea[name='q']", "Python")
    page1.press("textarea[name='q']", "Enter")
    print("User1 сделал поиск")
    context1.close()  # Очистка данных после использования

    # Повторяем для второго пользователя
    context2 = browser.new_context()
    page2 = context2.new_page()
    page2.goto("https://www.google.com")
    
    if page2.locator("button:has-text('Не интересует')").count() > 0:
        page2.locator("button:has-text('Не интересует')").click()
    
    page2.fill("textarea[name='q']", "Playwright")
    page2.press("textarea[name='q']", "Enter")
    print("User2 сделал поиск")
    context2.close()

    browser.close()  # Закрываем контексты и браузер

Примечание: Пример использует Google, но имейте в виду, что интерфейс и всплывающие окна могут различаться в зависимости от региона, языка или настроек аккаунта. Если код не работает у вас, попробуйте другой сайт или используйте проверку наличия элементов перед взаимодействием с ними.

Пояснения:

  • Каждый контекст имеет свои данные: куки, локальное хранилище, кэш, разрешения.

  • Закрытие контекста автоматически очищает все его данные и освобождает ресурсы — это основной способ «уборки».

  • Контексты полезны для параллельного тестирования нескольких пользователей или сценариев.

Песочница (Sandbox) — изоляция процессов

Контексты браузера дают изоляцию сессий (куки, storage), но не изолируют процессы на уровне операционной системы. Если вам нужна более глубокая изоляция — например, для запуска потенциально опасного кода или полной независимости задач — можно запускать несколько отдельных процессов браузера.

Песочница — это среда, в которой браузер или агент запускается полностью изолированно на уровне процессов:

browser1 = p.chromium.launch(headless=True)
browser2 = p.chromium.launch(headless=True)

Такой подход помогает избежать конфликтов между независимыми задачами и тестами на более глубоком уровне, чем просто контексты. Каждый процесс браузера полностью независим от других.

Анти-побочные эффекты

Анти-побочные эффекты — это методы и практики, которые минимизируют влияние одного действия на другое:

1. Закрытие контекста после использования

Это основной и рекомендуемый способ очистки. Закрытие контекста автоматически удаляет все куки, кэш, разрешения и освобождает ресурсы:

context = browser.new_context()
# ... работа с контекстом ...
context.close()  # всё очищено

В некоторых случаях может понадобиться частичная очистка без закрытия контекста:

  • context.clear_cookies() — очищает все куки, сохранённые в данном контексте.

  • context.clear_permissions() — очищает все разрешения, выданные сайту в этом контексте.

Но в большинстве случаев проще и надёжнее просто закрыть контекст и создать новый.

2. Отдельные страницы или контексты для действий

  • Не используйте одну страницу для разных сценариев.

  • Каждое действие или тест — новая вкладка или, лучше, новый контекст.

3. Фиксированные состояния

Перед тестом можно принудительно установить состояние страницы, например:

page.goto("https://example.com/reset")

Это гарантирует одинаковые условия для всех тестов.

Сохранение контекста — работа с сессиями

Мы говорили об очистке состояния, но иногда нужно обратное: сохранить состояние между запусками. Например, пройти авторизацию один раз и использовать её во всех последующих тестах. Это особенно полезно, когда авторизация требует двухфакторной аутентификации или занимает много времени.

В Playwright, как и в реальном браузере, это делается с помощью сессий и куки файлов.

Куки представляют собой небольшие фрагменты данных, которые веб-сервер отправляет браузеру. Эти данные могут включать идентификаторы сессий, настройки пользователя и другую информацию. В Playwright каждый контекст может работать со своими куками, и мы можем их добавлять программно:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context()

    # Добавление куки
    context.add_cookies([{
        'name': 'session_id',
        'value': '123456',
        'url': 'https://example.com'
    }])

    page = context.new_page()
    page.goto('https://example.com')

    # Получение куки
    cookies = context.cookies()
    print(cookies)

    browser.close()

Можно сохранять полное состояние контекста на диск и загружать его при следующем запуске. Это включает куки, локальное хранилище и другие данные сессии:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    
    # Создаём новый контекст
    context = browser.new_context()
    page = context.new_page()

    # Переходим на сайт
    page.goto("https://example.com/auth")

    # Вводим логин/пароль
    page.fill("input[name='username']", "my_username")
    page.fill("input[name='password']", "my_password")
    page.click("button[type='submit']")

    # Сохраняем состояние контекста на диск
    context.storage_state(path="state.json")
    print("Состояние пользователя сохранено!")

    browser.close()

Порой вместо ввода логина и пароля, прописывания кнопок быстрее просто руками авторизоваться. Можно открыть страницу авторизации и после этого поставить выполнение кода на паузу с помощью page.pause(). Далее можно заполнить все необходимые поля, нажать все необходимые кнопки, чебоксы и т.д. После чего в инспекторе нажать resume (или F8)

И потом загружаем это состояние, и наш сайт открывается в состоянии пройденной авторизации:

context = browser.new_context(storage_state="state.json")
page = context.new_page()
page.goto("https://example.com")  # сессия восстановлена

Файл state.json содержит куки и локальное хранилище, и это позволяет агенту "помнить" пользователя между тестами или запусками.

Загрузка и выгрузка файлов в Playwright

Автоматизация работы с файлами — загрузка документов на сайт и скачивание результатов — часто нужна для тестирования форм или сбора данных.

1. Загрузка файлов (download)

При клике на ссылку или кнопку браузер начинает скачивание файла. Чтобы корректно перехватить этот момент, используется контекст ожидания expect_download:

with page.expect_download() as download_info:
    page.click("text=Скачать файл")  # действие, которое инициирует загрузку

download = download_info.value      # объект скачанного файла
download.save_as("local_file.pdf")  # сохранение файла на диск

2. Выгрузка файлов (upload)

Для загрузки файлов на сайт используется метод set_input_files:

page.set_input_files("input[type=file]", "example_file.txt")

# Несколько файлов
page.set_input_files("input[type='file']", ["file1.txt", "file2.txt"])

Playwright напрямую подставляет файл в input[type=file], браузер «считает», что пользователь выбрал файл вручную. После этого можно нажимать кнопку отправки формы.

Важные моменты

  • expect_download используется только вместе с действием, запускающим скачивание

  • set_input_files работает без GUI-диалогов — не нужно взаимодействовать с системным окном выбора файлов

  • оба механизма не требуют таймаутов и sleep, так как встроены в систему ожиданий Playwright

Итоги

  • Изоляция обеспечивает независимость действий разных агентов и тестов, предотвращая побочные эффекты.

  • Контексты браузера дают изоляцию сессий (куки, storage, кэш), а отдельные процессы браузера — полную изоляцию на уровне ОС.

  • Закрытие контекста — основной способ очистки состояния; оно автоматически удаляет все данные и освобождает ресурсы.

  • Сохранение состояния контекста позволяет переиспользовать авторизацию и настройки между запусками.

Параллелизм и очередь задач; лимиты и троттлинг

Представьте: вам нужно собрать данные с 100 страниц сайта. Открывать их по одной — это займёт часы. Что если открыть все 100 одновременно? Браузер рухнет от нехватки памяти, или сайт заблокирует вас за DDoS. Нужен баланс: достаточный параллелизм для скорости, но не настолько большой, чтобы сломать систему или получить бан.

Когда вы автоматизируете браузер с помощью Playwright, очень часто возникает необходимость выполнять сразу несколько задач: открывать несколько страниц, собирать данные с разных источников или параллельно запускать тесты. Параллелизм позволяет делать это одновременно, экономя время. Однако нужно учитывать, что браузерные процессы потребляют ресурсы, поэтому слишком много одновременных задач может привести к сбоям или перегрузке системы.

Ключевые аспекты параллелизма и управления задачами:

  1. Параллелизм (Concurrency): В Playwright вы можете запускать несколько страниц или браузерных контекстов одновременно. В Python это удобно делать с помощью asyncio. Параллелизм позволяет ускорять сбор данных или выполнение тестов, но требует аккуратного управления ресурсами.
  2. Очередь задач (Task Queue): Когда задач слишком много, их удобно ставить в очередь и обрабатывать по очереди или в ограниченном параллелизме. Так вы контролируете нагрузку на систему и снижаете вероятность ошибок.
  3. Лимиты: Ограничение количества одновременно выполняемых задач предотвращает перегрузку процессора и памяти. Например, можно разрешить запуск не более 5 страниц одновременно, даже если всего задач 20.
  4. Троттлинг (Throttling): Иногда нужно контролировать скорость выполнения действий, например, чтобы не заблокировали сайт или API за слишком частые запросы. Троттлинг позволяет задать минимальные интервалы между задачами или запросами.
  5. Стабильность: Комбинация очереди, лимитов и троттлинга обеспечивает стабильную работу браузерного агента. Без этих механизмов параллельное выполнение может приводить к зависаниям, ошибкам загрузки страниц или неправильной работе селекторов.

Параллелизм в Playwright

Playwright поддерживает параллельную работу на уровне браузеров, контекстов и страниц, но использование синхронного API (sync_playwright) не даёт настоящего параллелизма — все действия выполняются последовательно, даже если открыто несколько страниц. Реальный параллелизм достигается при использовании асинхронного API (async/await) или запуска нескольких процессов.

Для одновременной загрузки страниц и неблокирующего выполнения кода следует использовать async_playwright и asyncio.

import asyncio
import time
from playwright.async_api import async_playwright


async def open_page(context, url, idx):
    start = time.perf_counter()
    print(f"[{idx}] START  {start:.3f}")

    page = await context.new_page()
    await page.goto(url)

    title = await page.title()
    end = time.perf_counter()

    print(f"[{idx}] END    {end:.3f} | duration: {end - start:.2f}s | {title}")
    await page.close()


async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        context = await browser.new_context()  # создаём общий контекст

        tasks = [open_page(context, "https://example.com", i) for i in range(1, 6)]

        await asyncio.gather(*tasks)
        await context.close()
        await browser.close()

asyncio.run(main())

В данном примере создаются пять страниц внутри одного контекста, которые одновременно загружают сайт. Это демонстрирует, как можно использовать параллелизм для ускорения процесса.

Важно: Обратите внимание, что мы создаём контекст явно через browser.new_context(), а затем страницы через context.new_page(). Если использовать browser.new_page() напрямую (как в старых примерах), Playwright создаст новый контекст для каждой страницы автоматически. Это удобно для простых сценариев, но при параллелизме резко увеличивает расход ресурсов — контекст гораздо «тяжелее» страницы. Кроме того, каждая страница будет иметь свои куки и storage, что может быть нежелательно.

Очередь задач

Очередь задач используется для ограничения количества одновременно выполняемых операций. Это важно при работе с Playwright, где каждая страница потребляет ресурсы и избыточный параллелизм может привести к ошибкам или блокировкам.

Пример реализации очереди задач:

import asyncio
from playwright.async_api import async_playwright

async def worker(name, queue, context):
    while True:
        try:
            url = queue.get_nowait()
        except asyncio.QueueEmpty:
            return

        print(f"[{name}] START {url}")
        page = await context.new_page()
        await page.goto(url)
        title = await page.title()
        print(f"[{name}] END   {url} → {title}")
        await page.close()

        queue.task_done()

async def main(urls):
    queue = asyncio.Queue()

    for url in urls:
        await queue.put(url)

    async with async_playwright() as p:
        browser = await p.chromium.launch()
        context = await browser.new_context()  # общий контекст для всех воркеров

        workers = [
            asyncio.create_task(worker(f"W{i}", queue, context))
            for i in range(1, 4)  # максимум 3 параллельные задачи
        ]

        await queue.join()      # ждём обработки всех URL
        for w in workers:
            w.cancel()

        await context.close()
        await browser.close()

urls = ["https://example.com", "https://example.org", "https://example.net"]

asyncio.run(main(urls))

В этом примере создаётся очередь для URL-адресов, которые необходимо загрузить. Рабочие потоки обрабатывают URL-адреса из очереди, что позволяет контролировать параллелизм и нагрузку, но не гарантирует порядок завершения операций. Все воркеры используют один контекст и разделяют состояние (куки, авторизацию).

Лимиты и троттлинг

При работе с параллельными задачами важно ограничивать количество одновременных операций и частоту их запуска. Лимиты защищают систему и браузер от перегрузки, а троттлинг позволяет контролировать скорость запросов и снижать риск блокировок со стороны сайта.

В асинхронном коде троттлинг обычно реализуется через задержки между задачами или ограничение числа одновременно выполняемых операций. Важный момент: лучше разделять лимиты параллелизма (через семафор) и паузы между запросами (после освобождения семафора), чтобы не блокировать слоты во время ожидания.

import asyncio
import random
from playwright.async_api import async_playwright


async def fetch_page(url, context, semaphore):
    async with semaphore:  # лимит параллельных задач
        page = await context.new_page()
        await page.goto(url)
        title = await page.title()
        print(title)
        await page.close()
    
    # Троттлинг после освобождения семафора
    await asyncio.sleep(random.uniform(0.8, 1.5))


async def main(urls):
    semaphore = asyncio.Semaphore(2)  # максимум 2 задачи одновременно

    async with async_playwright() as p:
        browser = await p.chromium.launch()
        context = await browser.new_context()

        tasks = [fetch_page(url, context, semaphore) for url in urls]

        await asyncio.gather(*tasks)
        await context.close()
        await browser.close()


urls = ["https://example.com", "https://example.org", "https://example.net"]

asyncio.run(main(urls))

В этом примере:

  • Semaphore ограничивает параллелизм — не более 2 страниц загружаются одновременно
  • sleep + random выполняется после освобождения семафора, контролируя частоту запросов без блокировки других задач
  • комбинация снижает нагрузку и делает поведение более «естественным», имитируя действия реального пользователя

Итоги

Параллелизм позволяет ускорять выполнение задач, но без контроля ресурсов легко перегрузить систему. Очередь задач помогает управлять порядком и количеством одновременных действий. Лимиты и троттлинг обеспечивают стабильность работы и предотвращают ошибки при высокой нагрузке. Правильное использование контекстов (через new_context() и context.new_page()) экономит ресурсы и позволяет контролировать изоляцию данных. Комбинированное использование этих механизмов делает браузерных агентов более надежными и предсказуемыми.

Задача 1

Какой метод используется для открытия новой страницы в браузере Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

Какой селектор используется для нахождения элементов по их стилям в Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 3

Для чего используют отдельные контексты браузера в Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 4

Что такое sandbox в контексте Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 5

Какой метод позволяет получить текст элемента на странице в Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 6

Для чего используется асинхронное программирование в Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 7

Что делают retries в Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 8

Что происходит при сохранении контекста в Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 9

Какой Python-механизм используется для ограничения числа одновременно выполняемых задач в примере с Playwright?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 10

Что делает throttling в браузерной автоматизации?

Выберите один вариант
Без регистрации · результат не сохраняется

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку